1 # -*- coding: utf-8 -*-
2 #----------------------------------------------------------
3 # OpenERP Web HTTP layer
4 #----------------------------------------------------------
27 import werkzeug.contrib.sessions
28 import werkzeug.datastructures
29 import werkzeug.exceptions
31 import werkzeug.wrappers
33 import werkzeug.routing as routing
41 _logger = logging.getLogger(__name__)
43 #----------------------------------------------------------
45 #----------------------------------------------------------
46 class WebRequest(object):
47 """ Parent class for all OpenERP Web request types, mostly deals with
48 initialization and setup of the request object (the dispatching itself has
49 to be handled by the subclasses)
51 :param request: a wrapped werkzeug Request object
52 :type request: :class:`werkzeug.wrappers.BaseRequest`
54 .. attribute:: httprequest
56 the original :class:`werkzeug.wrappers.Request` object provided to the
59 .. attribute:: httpsession
61 a :class:`~collections.Mapping` holding the HTTP session data for the
66 :class:`~collections.Mapping` of request parameters, not generally
67 useful as they're provided directly to the handler method as keyword
70 .. attribute:: session_id
72 opaque identifier for the :class:`session.OpenERPSession` instance of
75 .. attribute:: session
77 :class:`~session.OpenERPSession` instance for the current request
79 .. attribute:: context
81 :class:`~collections.Mapping` of context values for the current request
85 ``bool``, indicates whether the debug mode is active on the client
89 ``str``, the name of the database linked to the current request. Can be ``None``
90 if the current request uses the ``none`` authentication.
94 ``int``, the id of the user related to the current request. Can be ``None``
95 if the current request uses the ``none`` or the ``db`` authenticatoin.
97 def __init__(self, httprequest):
98 self.httprequest = httprequest
99 self.httpresponse = None
100 self.httpsession = httprequest.session
104 self.auth_method = None
107 self.func_request_type = None
109 def init(self, params):
110 self.params = dict(params)
111 # OpenERP session setup
112 self.session_id = self.params.pop("session_id", None)
113 if not self.session_id:
114 i0 = self.httprequest.cookies.get("instance0|session_id", None)
116 self.session_id = simplejson.loads(urllib2.unquote(i0))
118 self.session_id = uuid.uuid4().hex
119 self.session = self.httpsession.get(self.session_id)
121 self.session = OpenERPSession()
122 self.httpsession[self.session_id] = self.session
124 with set_request(self):
125 self.db = self.session._db or db_monodb()
128 # set db/uid trackers - they're cleaned up at the WSGI
129 # dispatching phase in openerp.service.wsgi_server.application
131 threading.current_thread().dbname = self.session._db
132 if self.session._uid:
133 threading.current_thread().uid = self.session._uid
135 self.context = self.params.pop('context', {})
136 self.debug = self.params.pop('debug', False) is not False
137 # Determine self.lang
138 lang = self.params.get('lang', None)
140 lang = self.context.get('lang')
142 lang = self.httprequest.cookies.get('lang')
144 lang = self.httprequest.accept_languages.best
147 # tranform 2 letters lang like 'en' into 5 letters like 'en_US'
148 lang = babel.core.LOCALE_ALIASES.get(lang, lang)
149 # we use _ as seprator where RFC2616 uses '-'
150 self.lang = lang.replace('-', '_')
152 def _authenticate(self):
153 if self.auth_method == "none":
156 elif self.auth_method == "db":
157 self.db = self.session._db or db_monodb()
159 raise SessionExpiredException("No valid database for request %s" % self.httprequest)
163 self.session.check_security()
164 except SessionExpiredException, e:
165 raise SessionExpiredException("Session expired for request %s" % self.httprequest)
166 self.db = self.session._db
167 self.uid = self.session._uid
172 The registry to the database linked to this request. Can be ``None`` if the current request uses the
173 ``none'' authentication.
175 return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
180 The cursor initialized for the current method call. If the current request uses the ``none`` authentication
181 trying to access this property will raise an exception.
183 # some magic to lazy create the cr
185 self._cr_cm = self.registry.cursor()
186 self._cr = self._cr_cm.__enter__()
189 def _call_function(self, *args, **kwargs):
192 # ugly syntax only to get the __exit__ arguments to pass to self._cr
194 class with_obj(object):
197 def __exit__(self, *args):
199 request._cr_cm.__exit__(*args)
200 request._cr_cm = None
204 if self.func_request_type != self._request_type:
205 raise Exception("%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \
206 % (self.func, self.httprequest.path, self.func_request_type, self._request_type))
207 return self.func(*args, **kwargs)
209 # just to be sure no one tries to re-use the request
213 def route(route, type="http", auth="user"):
215 Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
218 Decorator to put on a controller method to inform it does not require a user to be logged. When this decorator
219 is used, ``request.uid`` will be ``None``. The request will still try to detect the database and an exception
220 will be launched if there is no way to guess it.
222 :param route: string or array. The route part that will determine which http requests will match the decorated
223 method. Can be a single string or an array of strings. See werkzeug's routing documentation for the format of
224 route expression ( http://werkzeug.pocoo.org/docs/routing/ ).
225 :param type: The type of request, can be ``'http'`` or ``'json'``.
226 :param auth: The type of authentication method, can on of the following:
228 * ``auth``: The user must be authenticated.
229 * ``db``: There is no need for the user to be authenticated but there must be a way to find the current
231 * ``none``: The method is always active, even if there is no database. Mainly used by the framework and
232 authentication modules.
234 assert type in ["http", "json"]
235 assert auth in ["user", "db", "none"]
237 if isinstance(route, list):
242 if getattr(f, "auth", None) is None:
247 def reject_nonliteral(dct):
250 "Non literal contexts can not be sent to the server anymore (%r)" % (dct,))
253 class JsonRequest(WebRequest):
254 """ JSON-RPC2 over HTTP.
258 --> {"jsonrpc": "2.0",
260 "params": {"session_id": "SID",
265 <-- {"jsonrpc": "2.0",
266 "result": { "res1": "val1" },
269 Request producing a error::
271 --> {"jsonrpc": "2.0",
273 "params": {"session_id": "SID",
278 <-- {"jsonrpc": "2.0",
280 "message": "End user error message.",
281 "data": {"code": "codestring",
282 "debug": "traceback" } },
286 _request_type = "json"
288 def __init__(self, *args):
289 super(JsonRequest, self).__init__(*args)
291 self.jsonp_handler = None
293 args = self.httprequest.args
294 jsonp = args.get('jsonp')
297 request_id = args.get('id')
299 if jsonp and self.httprequest.method == 'POST':
300 # jsonp 2 steps step1 POST: save call
304 self.session.jsonp_requests[request_id] = self.httprequest.form['r']
305 headers=[('Content-Type', 'text/plain; charset=utf-8')]
306 r = werkzeug.wrappers.Response(request_id, headers=headers)
308 self.jsonp_handler = handler
310 elif jsonp and args.get('r'):
312 request = args.get('r')
313 elif jsonp and request_id:
314 # jsonp 2 steps step2 GET: run and return result
316 request = self.session.jsonp_requests.pop(request_id, "")
319 request = self.httprequest.stream.read()
321 # Read POST content or POST Form Data named "request"
322 self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
323 self.init(self.jsonrequest.get("params", {}))
326 """ Calls the method asked for by the JSON-RPC2 or JSONP request
328 :returns: an utf8 encoded JSON-RPC2 or JSONP reply
330 if self.jsonp_handler:
331 return self.jsonp_handler()
332 response = {"jsonrpc": "2.0" }
335 #if _logger.isEnabledFor(logging.DEBUG):
336 # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest))
337 response['id'] = self.jsonrequest.get('id')
338 response["result"] = self._call_function(**self.params)
339 except AuthenticationError, e:
340 _logger.exception("Exception during JSON request handling.")
341 se = serialize_exception(e)
344 'message': "OpenERP Session Invalid",
348 _logger.exception("Exception during JSON request handling.")
349 se = serialize_exception(e)
352 'message': "OpenERP Server Error",
356 response["error"] = error
358 if _logger.isEnabledFor(logging.DEBUG):
359 _logger.debug("<--\n%s", pprint.pformat(response))
362 # If we use jsonp, that's mean we are called from another host
363 # Some browser (IE and Safari) do no allow third party cookies
364 # We need then to manage http sessions manually.
365 response['httpsessionid'] = self.httpsession.sid
366 mime = 'application/javascript'
367 body = "%s(%s);" % (self.jsonp, simplejson.dumps(response),)
369 mime = 'application/json'
370 body = simplejson.dumps(response)
372 r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
375 def serialize_exception(e):
377 "name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__,
378 "debug": traceback.format_exc(),
379 "message": u"%s" % e,
380 "arguments": to_jsonable(e.args),
382 if isinstance(e, openerp.osv.osv.except_osv):
383 tmp["exception_type"] = "except_osv"
384 elif isinstance(e, openerp.exceptions.Warning):
385 tmp["exception_type"] = "warning"
386 elif isinstance(e, openerp.exceptions.AccessError):
387 tmp["exception_type"] = "access_error"
388 elif isinstance(e, openerp.exceptions.AccessDenied):
389 tmp["exception_type"] = "access_denied"
393 if isinstance(o, str) or isinstance(o,unicode) or isinstance(o, int) or isinstance(o, long) \
394 or isinstance(o, bool) or o is None or isinstance(o, float):
396 if isinstance(o, list) or isinstance(o, tuple):
397 return [to_jsonable(x) for x in o]
398 if isinstance(o, dict):
400 for k, v in o.items():
401 tmp[u"%s" % k] = to_jsonable(v)
406 """ Decorator marking the decorated method as being a handler for a
407 JSON-RPC request (the exact request path is specified via the
408 ``$(Controller._cp_path)/$methodname`` combination.
410 If the method is called, it will be provided with a :class:`JsonRequest`
411 instance and all ``params`` sent during the JSON-RPC request, apart from
412 the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
417 if f.__name__ == "index":
419 return route([base, os.path.join(base, "<path:_ignored_path>")], type="json", auth="user")(f)
421 class HttpRequest(WebRequest):
422 """ Regular GET/POST request
424 _request_type = "http"
426 def __init__(self, *args):
427 super(HttpRequest, self).__init__(*args)
428 params = dict(self.httprequest.args)
429 params.update(self.httprequest.form)
430 params.update(self.httprequest.files)
435 for key, value in self.httprequest.args.iteritems():
436 if isinstance(value, basestring) and len(value) < 1024:
439 akw[key] = type(value)
440 #_logger.debug("%s --> %s.%s %r", self.httprequest.func, func.im_class.__name__, func.__name__, akw)
442 r = self._call_function(**self.params)
443 except werkzeug.exceptions.HTTPException, e:
446 _logger.exception("An exception occured during an http request")
447 se = serialize_exception(e)
450 'message': "OpenERP Server Error",
453 r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps(error)))
456 r = werkzeug.wrappers.Response(status=204) # no content
457 if isinstance(r, (werkzeug.wrappers.BaseResponse, werkzeug.exceptions.HTTPException)):
458 _logger.debug('<-- %s', r)
460 _logger.debug("<-- size: %s", len(r))
463 def make_response(self, data, headers=None, cookies=None):
464 """ Helper for non-HTML responses, or HTML responses with custom
465 response headers or cookies.
467 While handlers can just return the HTML markup of a page they want to
468 send as a string if non-HTML data is returned they need to create a
469 complete response object, or the returned data will not be correctly
470 interpreted by the clients.
472 :param basestring data: response body
473 :param headers: HTTP headers to set on the response
474 :type headers: ``[(name, value)]``
475 :param collections.Mapping cookies: cookies to set on the client
477 response = werkzeug.wrappers.Response(data, headers=headers)
479 for k, v in cookies.iteritems():
480 response.set_cookie(k, v)
483 def not_found(self, description=None):
484 """ Helper for 404 response, return its result from the method
486 return werkzeug.exceptions.NotFound(description)
489 """ Decorator marking the decorated method as being a handler for a
490 normal HTTP request (the exact request path is specified via the
491 ``$(Controller._cp_path)/$methodname`` combination.
493 If the method is called, it will be provided with a :class:`HttpRequest`
494 instance and all ``params`` sent during the request (``GET`` and ``POST``
495 merged in the same dictionary), apart from the ``session_id``, ``context``
496 and ``debug`` keys (which are stripped out beforehand)
500 if f.__name__ == "index":
502 return route([base, os.path.join(base, "<path:_ignored_path>")], type="http", auth="user")(f)
504 #----------------------------------------------------------
505 # Local storage of requests
506 #----------------------------------------------------------
507 from werkzeug.local import LocalStack
509 _request_stack = LocalStack()
511 def set_request(request):
512 class with_obj(object):
514 _request_stack.push(request)
515 def __exit__(self, *args):
520 A global proxy that always redirect to the current request object.
522 request = _request_stack()
524 #----------------------------------------------------------
525 # Controller registration with a metaclass
526 #----------------------------------------------------------
529 controllers_per_module = {}
531 class ControllerType(type):
532 def __init__(cls, name, bases, attrs):
533 super(ControllerType, cls).__init__(name, bases, attrs)
535 # create wrappers for old-style methods with req as first argument
536 cls._methods_wrapper = {}
537 for k, v in attrs.items():
538 if inspect.isfunction(v):
539 spec = inspect.getargspec(v)
540 first_arg = spec.args[1] if len(spec.args) >= 2 else None
541 if first_arg in ["req", "request"]:
543 return lambda self, *args, **kwargs: nv(self, request, *args, **kwargs)
544 cls._methods_wrapper[k] = build_new(v)
546 # store the controller in the controllers list
547 name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls)
548 class_path = name_class[0].split(".")
549 if not class_path[:2] == ["openerp", "addons"]:
551 # we want to know all modules that have controllers
552 module = class_path[2]
553 controllers_per_module.setdefault(module, [])
554 # but we only store controllers directly inheriting from Controller
555 if not "Controller" in globals() or not Controller in bases:
557 controllers_per_module.setdefault(module, []).append(name_class)
559 class Controller(object):
560 __metaclass__ = ControllerType
562 def get_wrapped_method(self, name):
563 if name in self.__class__._methods_wrapper:
564 return functools.partial(self.__class__._methods_wrapper[name], self)
566 return getattr(self, name)
568 #############################
570 #############################
572 class AuthenticationError(Exception):
575 class SessionExpiredException(Exception):
578 class Service(object):
581 Use ``openerp.netsvc.dispatch_rpc()`` instead.
583 def __init__(self, session, service_name):
584 self.session = session
585 self.service_name = service_name
587 def __getattr__(self, method):
588 def proxy_method(*args):
589 result = openerp.netsvc.dispatch_rpc(self.service_name, method, args)
596 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
598 def __init__(self, session, model):
599 self.session = session
601 self.proxy = self.session.proxy('object')
603 def __getattr__(self, method):
604 self.session.assert_valid()
605 def proxy(*args, **kw):
606 # Can't provide any retro-compatibility for this case, so we check it and raise an Exception
607 # to tell the programmer to adapt his code
608 if not request.db or not request.uid or self.session._db != request.db \
609 or self.session._uid != request.uid:
610 raise Exception("Trying to use Model with badly configured database or user.")
612 mod = request.registry.get(self.model)
613 meth = getattr(mod, method)
615 result = meth(cr, request.uid, *args, **kw)
618 if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
622 result = [index[x] for x in args[0] if x in index]
626 class OpenERPSession(object):
628 An OpenERP RPC session, a given user can own multiple such sessions
631 .. attribute:: context
633 The session context, a ``dict``. Can be reloaded by calling
634 :meth:`openerpweb.openerpweb.OpenERPSession.get_context`
636 .. attribute:: domains_store
638 A ``dict`` matching domain keys to evaluable (but non-literal) domains.
640 Used to store references to non-literal domains which need to be
641 round-tripped to the client browser.
644 self._creation_time = time.time()
648 self._password = False
649 self._suicide = False
651 self.jsonp_requests = {} # FIXME use a LRU
653 def authenticate(self, db, login, password, env=None):
655 Authenticate the current user with the given db, login and password. If successful, store
656 the authentication parameters in the current session and request.
658 uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
662 self._password = password
666 if uid: self.get_context()
669 def check_security(self):
671 Chech the current authentication parameters to know if those are still valid. This method
672 should be called at each request. If the authentication fails, a ``SessionExpiredException``
675 if not self._db or not self._uid:
676 raise SessionExpiredException("Session expired")
677 import openerp.service.security as security
678 security.check(self._db, self._uid, self._password)
680 def get_context(self):
682 Re-initializes the current user's session context (based on
683 his preferences) by calling res.users.get_context() with the old
686 :returns: the new context
688 assert self._uid, "The user needs to be logged-in to initialize his context"
689 self.context = request.registry.get('res.users').context_get(request.cr, request.uid) or {}
690 self.context['uid'] = self._uid
691 self._fix_lang(self.context)
694 def _fix_lang(self, context):
695 """ OpenERP provides languages which may not make sense and/or may not
696 be understood by the web client's libraries.
700 :param dict context: context to fix
702 lang = context['lang']
704 # inane OpenERP locale
708 # lang to lang_REGION (datejs only handles lang_REGION, no bare langs)
709 if lang in babel.core.LOCALE_ALIASES:
710 lang = babel.core.LOCALE_ALIASES[lang]
712 context['lang'] = lang or 'en_US'
714 def send(self, service_name, method, *args):
717 Use ``openerp.netsvc.dispatch_rpc()`` instead.
719 return openerp.netsvc.dispatch_rpc(service_name, method, args)
721 def proxy(self, service):
724 Use ``openerp.netsvc.dispatch_rpc()`` instead.
726 return Service(self, service)
728 def assert_valid(self, force=False):
731 Use ``check_security()`` instead.
733 Ensures this session is valid (logged into the openerp server)
735 if self._uid and not force:
737 # TODO use authenticate instead of login
738 self._uid = self.proxy("common").login(self._db, self._login, self._password)
740 raise AuthenticationError("Authentication failure")
742 def ensure_valid(self):
745 Use ``check_security()`` instead.
749 self.assert_valid(True)
753 def execute(self, model, func, *l, **d):
756 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
758 model = self.model(model)
759 r = getattr(model, func)(*l, **d)
762 def exec_workflow(self, model, id, signal):
765 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
768 r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id)
771 def model(self, model):
774 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
776 Get an RPC proxy for the object ``model``, bound to this session.
778 :param model: an OpenERP model name
780 :rtype: a model object
782 if self._db == False:
783 raise SessionExpiredException("Session expired")
785 return Model(self, model)
787 #----------------------------------------------------------
788 # Session context manager
789 #----------------------------------------------------------
790 @contextlib.contextmanager
791 def session_context(httprequest, session_store, session_lock, sid):
794 httprequest.session = session_store.get(sid)
796 httprequest.session = session_store.new()
798 yield httprequest.session
800 # Remove all OpenERPSession instances with no uid, they're generated
801 # either by login process or by HTTP requests without an OpenERP
802 # session id, and are generally noise
803 removed_sessions = set()
804 for key, value in httprequest.session.items():
805 if not isinstance(value, OpenERPSession):
807 if getattr(value, '_suicide', False) or (
809 and not value.jsonp_requests
810 # FIXME do not use a fixed value
811 and value._creation_time + (60*5) < time.time()):
812 _logger.debug('remove session %s', key)
813 removed_sessions.add(key)
814 del httprequest.session[key]
818 # Re-load sessions from storage and merge non-literal
819 # contexts and domains (they're indexed by hash of the
820 # content so conflicts should auto-resolve), otherwise if
821 # two requests alter those concurrently the last to finish
822 # will overwrite the previous one, leading to loss of data
823 # (a non-literal is lost even though it was sent to the
824 # client and client errors)
826 # note that domains_store and contexts_store are append-only (we
827 # only ever add items to them), so we can just update one with the
828 # other to get the right result, if we want to merge the
829 # ``context`` dict we'll need something smarter
830 in_store = session_store.get(sid)
831 for k, v in httprequest.session.iteritems():
832 stored = in_store.get(k)
833 if stored and isinstance(v, OpenERPSession):
834 if hasattr(v, 'contexts_store'):
836 if hasattr(v, 'domains_store'):
838 if not hasattr(v, 'jsonp_requests'):
839 v.jsonp_requests = {}
840 v.jsonp_requests.update(getattr(
841 stored, 'jsonp_requests', {}))
844 for k, v in in_store.iteritems():
845 if k not in httprequest.session and k not in removed_sessions:
846 httprequest.session[k] = v
848 session_store.save(httprequest.session)
850 def session_gc(session_store):
851 if random.random() < 0.001:
852 # we keep session one week
853 last_week = time.time() - 60*60*24*7
854 for fname in os.listdir(session_store.path):
855 path = os.path.join(session_store.path, fname)
857 if os.path.getmtime(path) < last_week:
862 #----------------------------------------------------------
864 #----------------------------------------------------------
865 # Add potentially missing (older ubuntu) font mime types
866 mimetypes.add_type('application/font-woff', '.woff')
867 mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
868 mimetypes.add_type('application/x-font-ttf', '.ttf')
870 class DisableCacheMiddleware(object):
871 def __init__(self, app):
873 def __call__(self, environ, start_response):
874 def start_wrapped(status, headers):
875 referer = environ.get('HTTP_REFERER', '')
876 parsed = urlparse.urlparse(referer)
877 debug = parsed.query.count('debug') >= 1
880 unwanted_keys = ['Last-Modified']
882 new_headers = [('Cache-Control', 'no-cache')]
883 unwanted_keys += ['Expires', 'Etag', 'Cache-Control']
886 if k not in unwanted_keys:
887 new_headers.append((k, v))
889 start_response(status, new_headers)
890 return self.app(environ, start_wrapped)
894 username = getpass.getuser()
897 path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
900 except OSError as exc:
901 if exc.errno == errno.EEXIST:
902 # directory exists: ensure it has the correct permissions
903 # this will fail if the directory is not owned by the current user
910 """Root WSGI application for the OpenERP Web Client.
917 self.db_routers_lock = threading.Lock()
921 # Setup http sessions
922 path = session_path()
923 self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path)
924 self.session_lock = threading.Lock()
925 _logger.debug('HTTP sessions stored in: %s', path)
928 def __call__(self, environ, start_response):
929 """ Handle a WSGI request
931 return self.dispatch(environ, start_response)
933 def dispatch(self, environ, start_response):
935 Performs the actual WSGI dispatching for the application, may be
936 wrapped during the initialization of the object.
938 Call the object directly.
940 httprequest = werkzeug.wrappers.Request(environ)
941 httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict
942 httprequest.app = self
944 sid = httprequest.cookies.get('sid')
946 sid = httprequest.args.get('sid')
948 session_gc(self.session_store)
950 with session_context(httprequest, self.session_store, self.session_lock, sid) as session:
951 request = self._build_request(httprequest)
955 updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db)
957 with self.db_routers_lock:
958 del self.db_routers[db]
960 with set_request(request):
962 result = request.dispatch()
965 openerp.modules.registry.RegistryManager.signal_caches_change(db)
967 if isinstance(result, basestring):
968 headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
969 response = werkzeug.wrappers.Response(result, headers=headers)
973 if hasattr(response, 'set_cookie'):
974 response.set_cookie('sid', session.sid)
976 return response(environ, start_response)
978 def _build_request(self, httprequest):
979 if httprequest.args.get('jsonp'):
980 return JsonRequest(httprequest)
982 content = httprequest.stream.read()
984 httprequest.stream = cStringIO.StringIO(content)
986 simplejson.loads(content)
987 return JsonRequest(httprequest)
989 return HttpRequest(httprequest)
991 def load_addons(self):
992 """ Load all addons from addons patch containg static files and
993 controllers and configure them. """
995 for addons_path in openerp.modules.module.ad_paths:
996 for module in sorted(os.listdir(str(addons_path))):
997 if module not in addons_module:
998 manifest_path = os.path.join(addons_path, module, '__openerp__.py')
999 path_static = os.path.join(addons_path, module, 'static')
1000 if os.path.isfile(manifest_path) and os.path.isdir(path_static):
1001 manifest = ast.literal_eval(open(manifest_path).read())
1002 manifest['addons_path'] = addons_path
1003 _logger.debug("Loading %s", module)
1004 if 'openerp.addons' in sys.modules:
1005 m = __import__('openerp.addons.' + module)
1007 m = __import__(module)
1008 addons_module[module] = m
1009 addons_manifest[module] = manifest
1010 self.statics['/%s/static' % module] = path_static
1012 app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics)
1013 self.dispatch = DisableCacheMiddleware(app)
1015 def _build_router(self, db):
1016 _logger.info("Generating routing configuration for database %s" % db)
1017 routing_map = routing.Map()
1019 def gen(modules, nodb_only):
1020 for module in modules:
1021 for v in controllers_per_module[module]:
1024 subclasses = cls.__subclasses__()
1025 subclasses = [c for c in subclasses if c.__module__.split(".")[:2] == ["openerp", "addons"] and \
1026 cls.__module__.split(".")[2] in modules]
1028 name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
1029 cls = type(name, tuple(reversed(subclasses)), {})
1032 members = inspect.getmembers(o)
1033 for mk, mv in members:
1034 if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and \
1035 nodb_only == (getattr(mv, "auth", None) == "none"):
1036 function = (o.get_wrapped_method(mk), mv)
1037 for url in mv.routes:
1038 if getattr(mv, "combine", False):
1039 url = os.path.join(o._cp_path, url)
1040 if url.endswith("/") and len(url) > 1:
1042 routing_map.add(routing.Rule(url, endpoint=function))
1044 modules_set = set(controllers_per_module.keys())
1045 modules_set -= set("web")
1046 # building all none methods
1047 gen(["web"] + sorted(modules_set), True)
1051 registry = openerp.modules.registry.RegistryManager.get(db)
1052 with registry.cursor() as cr:
1053 m = registry.get('ir.module.module')
1054 ids = m.search(cr, openerp.SUPERUSER_ID, [('state','=','installed')])
1055 installed = set([x['name'] for x in m.read(cr, 1, ids, ['name'])])
1056 modules_set = modules_set.intersection(set(installed))
1057 modules = ["web"] + sorted(modules_set)
1058 # building all other methods
1059 gen(["web"] + sorted(modules_set), False)
1063 def get_db_router(self, db):
1064 with self.db_routers_lock:
1065 router = self.db_routers.get(db)
1067 router = self._build_router(db)
1068 with self.db_routers_lock:
1069 router = self.db_routers[db] = router
1072 def find_handler(self):
1074 Tries to discover the controller handling the request for the path
1075 specified by the provided parameters
1077 :param path: path to match
1078 :returns: a callable matching the path sections
1079 :rtype: ``Controller | None``
1081 path = request.httprequest.path
1082 urls = self.get_db_router(request.db).bind("")
1083 matched, arguments = urls.match(path)
1084 arguments = dict([(k, v) for k, v in arguments.items() if not k.startswith("_ignored_")])
1085 func, original = matched
1087 def nfunc(*args, **kwargs):
1088 kwargs.update(arguments)
1089 return func(*args, **kwargs)
1091 request.func = nfunc
1092 request.auth_method = getattr(original, "auth", "user")
1093 request.func_request_type = original.exposed
1096 proxy = request.session.proxy("db")
1098 h = request.httprequest.environ['HTTP_HOST'].split(':')[0]
1100 r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
1101 dbs = [i for i in dbs if re.match(r, i)]
1107 # 1 try the db in the url
1108 db_url = request.params.get('db')
1115 # ignore access denied
1118 # 2 use the database from the cookie if it's listable and still listed
1119 cookie_db = request.httprequest.cookies.get('last_used_database')
1120 if cookie_db in dbs:
1123 # 3 use the first db
1126 return db.lower() if db else db
1128 def wsgi_postload():
1129 openerp.wsgi.register_wsgi_handler(Root())