2011-09-02 08:58:53 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2011-10-05 17:58:26 +00:00
|
|
|
#----------------------------------------------------------
|
|
|
|
# OpenERP Web HTTP layer
|
|
|
|
#----------------------------------------------------------
|
|
|
|
import ast
|
2012-01-19 14:48:37 +00:00
|
|
|
import cgi
|
2011-09-02 08:58:53 +00:00
|
|
|
import contextlib
|
2011-10-05 17:58:26 +00:00
|
|
|
import functools
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import pprint
|
|
|
|
import sys
|
2011-11-15 16:12:51 +00:00
|
|
|
import threading
|
2012-01-12 12:52:07 +00:00
|
|
|
import time
|
2011-10-05 17:58:26 +00:00
|
|
|
import traceback
|
2011-12-16 00:44:02 +00:00
|
|
|
import urllib
|
2011-10-05 17:58:26 +00:00
|
|
|
import uuid
|
|
|
|
import xmlrpclib
|
2011-09-02 08:58:53 +00:00
|
|
|
|
2011-10-05 17:58:26 +00:00
|
|
|
import simplejson
|
2011-09-02 08:58:53 +00:00
|
|
|
import werkzeug.contrib.sessions
|
2011-10-05 17:58:26 +00:00
|
|
|
import werkzeug.datastructures
|
|
|
|
import werkzeug.exceptions
|
|
|
|
import werkzeug.utils
|
|
|
|
import werkzeug.wrappers
|
|
|
|
import werkzeug.wsgi
|
|
|
|
|
2012-02-10 10:34:26 +00:00
|
|
|
from . import nonliterals
|
|
|
|
from . import session
|
|
|
|
from . import openerplib
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
|
|
|
|
'WebRequest', 'JsonRequest', 'HttpRequest']
|
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
#----------------------------------------------------------
|
|
|
|
# OpenERP Web RequestHandler
|
|
|
|
#----------------------------------------------------------
|
|
|
|
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
|
|
|
|
to be handled by the subclasses)
|
|
|
|
|
|
|
|
:param request: a wrapped werkzeug Request object
|
|
|
|
:type request: :class:`werkzeug.wrappers.BaseRequest`
|
|
|
|
:param config: configuration object
|
|
|
|
|
|
|
|
.. attribute:: httprequest
|
|
|
|
|
|
|
|
the original :class:`werkzeug.wrappers.Request` object provided to the
|
|
|
|
request
|
|
|
|
|
|
|
|
.. attribute:: httpsession
|
|
|
|
|
|
|
|
a :class:`~collections.Mapping` holding the HTTP session data for the
|
|
|
|
current http session
|
|
|
|
|
|
|
|
.. attribute:: config
|
|
|
|
|
|
|
|
config parameter provided to the request object
|
|
|
|
|
|
|
|
.. attribute:: params
|
|
|
|
|
|
|
|
:class:`~collections.Mapping` of request parameters, not generally
|
|
|
|
useful as they're provided directly to the handler method as keyword
|
|
|
|
arguments
|
|
|
|
|
|
|
|
.. attribute:: session_id
|
|
|
|
|
|
|
|
opaque identifier for the :class:`session.OpenERPSession` instance of
|
|
|
|
the current request
|
|
|
|
|
|
|
|
.. attribute:: session
|
|
|
|
|
|
|
|
:class:`~session.OpenERPSession` instance for the current request
|
|
|
|
|
|
|
|
.. attribute:: context
|
|
|
|
|
|
|
|
:class:`~collections.Mapping` of context values for the current request
|
|
|
|
|
|
|
|
.. attribute:: debug
|
|
|
|
|
|
|
|
``bool``, indicates whether the debug mode is active on the client
|
|
|
|
"""
|
|
|
|
def __init__(self, request, config):
|
|
|
|
self.httprequest = request
|
|
|
|
self.httpresponse = None
|
|
|
|
self.httpsession = request.session
|
|
|
|
self.config = config
|
|
|
|
|
|
|
|
def init(self, params):
|
|
|
|
self.params = dict(params)
|
|
|
|
# OpenERP session setup
|
|
|
|
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
|
2012-01-19 09:06:48 +00:00
|
|
|
self.session = self.httpsession.get(self.session_id)
|
|
|
|
if not self.session:
|
|
|
|
self.httpsession[self.session_id] = self.session = session.OpenERPSession()
|
2011-10-05 17:58:26 +00:00
|
|
|
self.session.config = self.config
|
|
|
|
self.context = self.params.pop('context', None)
|
|
|
|
self.debug = self.params.pop('debug', False) != False
|
|
|
|
|
|
|
|
class JsonRequest(WebRequest):
|
|
|
|
""" JSON-RPC2 over HTTP.
|
|
|
|
|
|
|
|
Sucessful request::
|
|
|
|
|
|
|
|
--> {"jsonrpc": "2.0",
|
|
|
|
"method": "call",
|
|
|
|
"params": {"session_id": "SID",
|
|
|
|
"context": {},
|
|
|
|
"arg1": "val1" },
|
|
|
|
"id": null}
|
|
|
|
|
|
|
|
<-- {"jsonrpc": "2.0",
|
|
|
|
"result": { "res1": "val1" },
|
|
|
|
"id": null}
|
|
|
|
|
|
|
|
Request producing a error::
|
|
|
|
|
|
|
|
--> {"jsonrpc": "2.0",
|
|
|
|
"method": "call",
|
|
|
|
"params": {"session_id": "SID",
|
|
|
|
"context": {},
|
|
|
|
"arg1": "val1" },
|
|
|
|
"id": null}
|
|
|
|
|
|
|
|
<-- {"jsonrpc": "2.0",
|
|
|
|
"error": {"code": 1,
|
|
|
|
"message": "End user error message.",
|
|
|
|
"data": {"code": "codestring",
|
|
|
|
"debug": "traceback" } },
|
|
|
|
"id": null}
|
|
|
|
|
|
|
|
"""
|
2011-10-24 09:07:27 +00:00
|
|
|
def dispatch(self, controller, method):
|
|
|
|
""" Calls the method asked for by the JSON-RPC2 or JSONP request
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
:param controller: the instance of the controller which received the request
|
|
|
|
:param method: the method which received the request
|
|
|
|
|
2011-10-24 09:07:27 +00:00
|
|
|
:returns: an utf8 encoded JSON-RPC2 or JSONP reply
|
2011-10-05 17:58:26 +00:00
|
|
|
"""
|
2011-12-14 09:29:33 +00:00
|
|
|
args = self.httprequest.args
|
2011-12-16 16:47:39 +00:00
|
|
|
jsonp = args.get('jsonp')
|
2011-12-15 12:07:32 +00:00
|
|
|
requestf = None
|
|
|
|
request = None
|
2012-01-18 19:04:09 +00:00
|
|
|
request_id = args.get('id')
|
2011-12-14 09:29:33 +00:00
|
|
|
|
2011-12-16 00:44:02 +00:00
|
|
|
if jsonp and self.httprequest.method == 'POST':
|
2011-12-14 09:29:33 +00:00
|
|
|
# jsonp 2 steps step1 POST: save call
|
|
|
|
self.init(args)
|
2012-01-16 14:55:24 +00:00
|
|
|
self.session.jsonp_requests[request_id] = self.httprequest.form['r']
|
2011-12-14 09:29:33 +00:00
|
|
|
headers=[('Content-Type', 'text/plain; charset=utf-8')]
|
|
|
|
r = werkzeug.wrappers.Response(request_id, headers=headers)
|
|
|
|
return r
|
2011-12-16 16:47:39 +00:00
|
|
|
elif jsonp and args.get('r'):
|
|
|
|
# jsonp method GET
|
|
|
|
request = args.get('r')
|
2012-01-18 19:04:09 +00:00
|
|
|
elif jsonp and request_id:
|
2011-12-14 09:29:33 +00:00
|
|
|
# jsonp 2 steps step2 GET: run and return result
|
|
|
|
self.init(args)
|
2012-01-18 19:04:09 +00:00
|
|
|
request = self.session.jsonp_requests.pop(request_id, "")
|
2011-10-24 09:07:27 +00:00
|
|
|
else:
|
2011-12-14 09:29:33 +00:00
|
|
|
# regular jsonrpc2
|
|
|
|
requestf = self.httprequest.stream
|
2011-10-24 09:07:27 +00:00
|
|
|
|
2011-10-05 17:58:26 +00:00
|
|
|
response = {"jsonrpc": "2.0" }
|
|
|
|
error = None
|
|
|
|
try:
|
|
|
|
# Read POST content or POST Form Data named "request"
|
|
|
|
if requestf:
|
|
|
|
self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
|
|
|
|
else:
|
|
|
|
self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
|
|
|
|
self.init(self.jsonrequest.get("params", {}))
|
|
|
|
if _logger.isEnabledFor(logging.DEBUG):
|
|
|
|
_logger.debug("--> %s.%s\n%s", controller.__class__.__name__, method.__name__, pprint.pformat(self.jsonrequest))
|
|
|
|
response['id'] = self.jsonrequest.get('id')
|
|
|
|
response["result"] = method(controller, self, **self.params)
|
|
|
|
except openerplib.AuthenticationError:
|
|
|
|
error = {
|
|
|
|
'code': 100,
|
|
|
|
'message': "OpenERP Session Invalid",
|
|
|
|
'data': {
|
|
|
|
'type': 'session_invalid',
|
|
|
|
'debug': traceback.format_exc()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
except xmlrpclib.Fault, e:
|
|
|
|
error = {
|
|
|
|
'code': 200,
|
|
|
|
'message': "OpenERP Server Error",
|
|
|
|
'data': {
|
|
|
|
'type': 'server_exception',
|
|
|
|
'fault_code': e.faultCode,
|
|
|
|
'debug': "Client %s\nServer %s" % (
|
|
|
|
"".join(traceback.format_exception("", None, sys.exc_traceback)), e.faultString)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
except Exception:
|
|
|
|
logging.getLogger(__name__ + '.JSONRequest.dispatch').exception\
|
|
|
|
("An error occured while handling a json request")
|
|
|
|
error = {
|
|
|
|
'code': 300,
|
|
|
|
'message': "OpenERP WebClient Error",
|
|
|
|
'data': {
|
|
|
|
'type': 'client_exception',
|
|
|
|
'debug': "Client %s" % traceback.format_exc()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if error:
|
|
|
|
response["error"] = error
|
2011-09-02 08:58:53 +00:00
|
|
|
|
2011-10-05 17:58:26 +00:00
|
|
|
if _logger.isEnabledFor(logging.DEBUG):
|
|
|
|
_logger.debug("<--\n%s", pprint.pformat(response))
|
2011-10-24 09:07:27 +00:00
|
|
|
|
2011-12-14 09:29:33 +00:00
|
|
|
if jsonp:
|
|
|
|
mime = 'application/javascript'
|
|
|
|
body = "%s(%s);" % (jsonp, simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),)
|
|
|
|
else:
|
|
|
|
mime = 'application/json'
|
|
|
|
body = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
|
|
|
|
|
|
|
|
r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
|
|
|
|
return r
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
def jsonrequest(f):
|
|
|
|
""" Decorator marking the decorated method as being a handler for a
|
|
|
|
JSON-RPC request (the exact request path is specified via the
|
|
|
|
``$(Controller._cp_path)/$methodname`` combination.
|
|
|
|
|
|
|
|
If the method is called, it will be provided with a :class:`JsonRequest`
|
|
|
|
instance and all ``params`` sent during the JSON-RPC request, apart from
|
|
|
|
the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
|
|
|
|
beforehand)
|
|
|
|
"""
|
|
|
|
@functools.wraps(f)
|
|
|
|
def json_handler(controller, request, config):
|
2011-10-24 09:07:27 +00:00
|
|
|
return JsonRequest(request, config).dispatch(controller, f)
|
2011-10-05 17:58:26 +00:00
|
|
|
json_handler.exposed = True
|
|
|
|
return json_handler
|
|
|
|
|
|
|
|
class HttpRequest(WebRequest):
|
|
|
|
""" Regular GET/POST request
|
|
|
|
"""
|
|
|
|
def dispatch(self, controller, method):
|
|
|
|
params = dict(self.httprequest.args)
|
|
|
|
params.update(self.httprequest.form)
|
|
|
|
params.update(self.httprequest.files)
|
|
|
|
self.init(params)
|
|
|
|
akw = {}
|
|
|
|
for key, value in self.httprequest.args.iteritems():
|
|
|
|
if isinstance(value, basestring) and len(value) < 1024:
|
|
|
|
akw[key] = value
|
|
|
|
else:
|
|
|
|
akw[key] = type(value)
|
|
|
|
_logger.debug("%s --> %s.%s %r", self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
|
2012-01-19 14:48:37 +00:00
|
|
|
try:
|
|
|
|
r = method(controller, self, **self.params)
|
|
|
|
except xmlrpclib.Fault, e:
|
|
|
|
r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({
|
|
|
|
'code': 200,
|
|
|
|
'message': "OpenERP Server Error",
|
|
|
|
'data': {
|
|
|
|
'type': 'server_exception',
|
|
|
|
'fault_code': e.faultCode,
|
|
|
|
'debug': "Server %s\nClient %s" % (
|
|
|
|
e.faultString, traceback.format_exc())
|
|
|
|
}
|
|
|
|
})))
|
|
|
|
except Exception:
|
|
|
|
logging.getLogger(__name__ + '.HttpRequest.dispatch').exception(
|
|
|
|
"An error occurred while handling a json request")
|
|
|
|
r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({
|
|
|
|
'code': 300,
|
|
|
|
'message': "OpenERP WebClient Error",
|
|
|
|
'data': {
|
|
|
|
'type': 'client_exception',
|
|
|
|
'debug': "Client %s" % traceback.format_exc()
|
|
|
|
}
|
|
|
|
})))
|
2011-10-05 17:58:26 +00:00
|
|
|
if self.debug or 1:
|
2011-10-13 08:13:51 +00:00
|
|
|
if isinstance(r, (werkzeug.wrappers.BaseResponse, werkzeug.exceptions.HTTPException)):
|
2011-10-05 17:58:26 +00:00
|
|
|
_logger.debug('<-- %s', r)
|
|
|
|
else:
|
|
|
|
_logger.debug("<-- size: %s", len(r))
|
|
|
|
return r
|
|
|
|
|
|
|
|
def make_response(self, data, headers=None, cookies=None):
|
|
|
|
""" Helper for non-HTML responses, or HTML responses with custom
|
|
|
|
response headers or cookies.
|
|
|
|
|
|
|
|
While handlers can just return the HTML markup of a page they want to
|
|
|
|
send as a string if non-HTML data is returned they need to create a
|
|
|
|
complete response object, or the returned data will not be correctly
|
|
|
|
interpreted by the clients.
|
|
|
|
|
|
|
|
:param basestring data: response body
|
|
|
|
:param headers: HTTP headers to set on the response
|
|
|
|
:type headers: ``[(name, value)]``
|
|
|
|
:param collections.Mapping cookies: cookies to set on the client
|
|
|
|
"""
|
|
|
|
response = werkzeug.wrappers.Response(data, headers=headers)
|
|
|
|
if cookies:
|
|
|
|
for k, v in cookies.iteritems():
|
|
|
|
response.set_cookie(k, v)
|
|
|
|
return response
|
|
|
|
|
|
|
|
def not_found(self, description=None):
|
|
|
|
""" Helper for 404 response, return its result from the method
|
|
|
|
"""
|
|
|
|
return werkzeug.exceptions.NotFound(description)
|
|
|
|
|
|
|
|
def httprequest(f):
|
|
|
|
""" Decorator marking the decorated method as being a handler for a
|
|
|
|
normal HTTP request (the exact request path is specified via the
|
|
|
|
``$(Controller._cp_path)/$methodname`` combination.
|
|
|
|
|
|
|
|
If the method is called, it will be provided with a :class:`HttpRequest`
|
|
|
|
instance and all ``params`` sent during the request (``GET`` and ``POST``
|
|
|
|
merged in the same dictionary), apart from the ``session_id``, ``context``
|
|
|
|
and ``debug`` keys (which are stripped out beforehand)
|
|
|
|
"""
|
|
|
|
@functools.wraps(f)
|
|
|
|
def http_handler(controller, request, config):
|
|
|
|
return HttpRequest(request, config).dispatch(controller, f)
|
|
|
|
http_handler.exposed = True
|
|
|
|
return http_handler
|
|
|
|
|
|
|
|
#----------------------------------------------------------
|
|
|
|
# OpenERP Web werkzeug Session Managment wraped using with
|
|
|
|
#----------------------------------------------------------
|
2011-09-02 08:58:53 +00:00
|
|
|
STORES = {}
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
2011-10-05 17:58:26 +00:00
|
|
|
def session_context(request, storage_path, session_cookie='sessionid'):
|
2011-11-15 16:12:51 +00:00
|
|
|
session_store, session_lock = STORES.get(storage_path, (None, None))
|
2011-09-02 08:58:53 +00:00
|
|
|
if not session_store:
|
|
|
|
session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
|
|
|
|
storage_path)
|
2011-11-15 16:12:51 +00:00
|
|
|
session_lock = threading.Lock()
|
|
|
|
STORES[storage_path] = session_store, session_lock
|
2011-09-02 08:58:53 +00:00
|
|
|
|
|
|
|
sid = request.cookies.get(session_cookie)
|
2011-11-15 16:12:51 +00:00
|
|
|
with session_lock:
|
|
|
|
if sid:
|
|
|
|
request.session = session_store.get(sid)
|
|
|
|
else:
|
|
|
|
request.session = session_store.new()
|
2011-09-02 08:58:53 +00:00
|
|
|
|
2011-09-20 21:51:37 +00:00
|
|
|
try:
|
|
|
|
yield request.session
|
|
|
|
finally:
|
2011-10-17 11:07:56 +00:00
|
|
|
# Remove all OpenERPSession instances with no uid, they're generated
|
|
|
|
# either by login process or by HTTP requests without an OpenERP
|
|
|
|
# session id, and are generally noise
|
2012-01-23 13:53:25 +00:00
|
|
|
removed_sessions = set()
|
2011-10-17 11:07:56 +00:00
|
|
|
for key, value in request.session.items():
|
2012-02-10 16:43:09 +00:00
|
|
|
if not isinstance(value, session.OpenERPSession):
|
|
|
|
continue
|
|
|
|
if getattr(value, '_suicide', False) or (
|
|
|
|
not value._uid
|
|
|
|
and not value.jsonp_requests
|
|
|
|
# FIXME do not use a fixed value
|
|
|
|
and value._creation_time + (60*5) < time.time()):
|
2012-01-13 14:58:32 +00:00
|
|
|
_logger.debug('remove session %s', key)
|
2012-01-23 13:53:25 +00:00
|
|
|
removed_sessions.add(key)
|
2011-10-17 11:07:56 +00:00
|
|
|
del request.session[key]
|
|
|
|
|
2011-11-15 16:12:51 +00:00
|
|
|
with session_lock:
|
|
|
|
if sid:
|
|
|
|
# Re-load sessions from storage and merge non-literal
|
|
|
|
# contexts and domains (they're indexed by hash of the
|
|
|
|
# content so conflicts should auto-resolve), otherwise if
|
|
|
|
# two requests alter those concurrently the last to finish
|
|
|
|
# will overwrite the previous one, leading to loss of data
|
|
|
|
# (a non-literal is lost even though it was sent to the
|
|
|
|
# client and client errors)
|
|
|
|
#
|
|
|
|
# note that domains_store and contexts_store are append-only (we
|
|
|
|
# only ever add items to them), so we can just update one with the
|
|
|
|
# other to get the right result, if we want to merge the
|
|
|
|
# ``context`` dict we'll need something smarter
|
|
|
|
in_store = session_store.get(sid)
|
|
|
|
for k, v in request.session.iteritems():
|
|
|
|
stored = in_store.get(k)
|
2011-12-19 10:43:16 +00:00
|
|
|
if stored and isinstance(v, session.OpenERPSession):
|
2011-11-15 16:12:51 +00:00
|
|
|
v.contexts_store.update(stored.contexts_store)
|
|
|
|
v.domains_store.update(stored.domains_store)
|
2011-12-20 11:47:32 +00:00
|
|
|
if not hasattr(v, 'jsonp_requests'):
|
|
|
|
v.jsonp_requests = {}
|
|
|
|
v.jsonp_requests.update(getattr(
|
|
|
|
stored, 'jsonp_requests', {}))
|
2011-11-15 16:12:51 +00:00
|
|
|
|
2011-11-22 12:08:06 +00:00
|
|
|
# add missing keys
|
|
|
|
for k, v in in_store.iteritems():
|
2012-01-17 17:06:36 +00:00
|
|
|
if k not in request.session and k not in removed_sessions:
|
2011-11-22 12:08:06 +00:00
|
|
|
request.session[k] = v
|
|
|
|
|
2011-11-15 16:12:51 +00:00
|
|
|
session_store.save(request.session)
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
#----------------------------------------------------------
|
|
|
|
# OpenERP Web Module/Controller Loading and URL Routing
|
|
|
|
#----------------------------------------------------------
|
|
|
|
addons_module = {}
|
|
|
|
addons_manifest = {}
|
2012-02-23 12:28:29 +00:00
|
|
|
controllers_class = []
|
2011-10-05 17:58:26 +00:00
|
|
|
controllers_object = {}
|
|
|
|
controllers_path = {}
|
|
|
|
|
|
|
|
class ControllerType(type):
|
|
|
|
def __init__(cls, name, bases, attrs):
|
|
|
|
super(ControllerType, cls).__init__(name, bases, attrs)
|
2012-02-23 12:28:29 +00:00
|
|
|
controllers_class.append(("%s.%s" % (cls.__module__, cls.__name__), cls))
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
class Controller(object):
|
|
|
|
__metaclass__ = ControllerType
|
|
|
|
|
|
|
|
class Root(object):
|
|
|
|
"""Root WSGI application for the OpenERP Web Client.
|
|
|
|
|
|
|
|
:param options: mandatory initialization options object, must provide
|
|
|
|
the following attributes:
|
|
|
|
|
|
|
|
``server_host`` (``str``)
|
|
|
|
hostname of the OpenERP server to dispatch RPC to
|
|
|
|
``server_port`` (``int``)
|
|
|
|
RPC port of the OpenERP server
|
|
|
|
``serve_static`` (``bool | None``)
|
|
|
|
whether this application should serve the various
|
|
|
|
addons's static files
|
|
|
|
``storage_path`` (``str``)
|
|
|
|
filesystem path where HTTP session data will be stored
|
|
|
|
``dbfilter`` (``str``)
|
|
|
|
only used in case the list of databases is requested
|
|
|
|
by the server, will be filtered by this pattern
|
|
|
|
"""
|
2012-01-16 10:43:29 +00:00
|
|
|
def __init__(self, options, openerp_addons_namespace=True):
|
2011-10-05 17:58:26 +00:00
|
|
|
self.root = '/web/webclient/home'
|
|
|
|
self.config = options
|
|
|
|
|
|
|
|
if self.config.backend == 'local':
|
2012-01-05 16:56:33 +00:00
|
|
|
conn = LocalConnector()
|
2011-10-05 17:58:26 +00:00
|
|
|
else:
|
|
|
|
conn = openerplib.get_connector(hostname=self.config.server_host,
|
|
|
|
port=self.config.server_port)
|
|
|
|
self.config.connector = conn
|
|
|
|
|
|
|
|
self.session_cookie = 'sessionid'
|
|
|
|
self.addons = {}
|
|
|
|
|
2012-01-16 10:43:29 +00:00
|
|
|
static_dirs = self._load_addons(openerp_addons_namespace)
|
2011-10-05 17:58:26 +00:00
|
|
|
if options.serve_static:
|
|
|
|
self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
|
|
|
|
self.dispatch, static_dirs)
|
|
|
|
|
|
|
|
if options.session_storage:
|
|
|
|
if not os.path.exists(options.session_storage):
|
|
|
|
os.mkdir(options.session_storage, 0700)
|
|
|
|
self.session_storage = options.session_storage
|
2011-12-19 10:26:45 +00:00
|
|
|
_logger.debug('HTTP sessions stored in: %s', self.session_storage)
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
def __call__(self, environ, start_response):
|
|
|
|
""" Handle a WSGI request
|
|
|
|
"""
|
|
|
|
return self.dispatch(environ, start_response)
|
|
|
|
|
|
|
|
def dispatch(self, environ, start_response):
|
|
|
|
"""
|
|
|
|
Performs the actual WSGI dispatching for the application, may be
|
|
|
|
wrapped during the initialization of the object.
|
|
|
|
|
|
|
|
Call the object directly.
|
|
|
|
"""
|
|
|
|
request = werkzeug.wrappers.Request(environ)
|
|
|
|
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
|
2011-12-19 14:57:59 +00:00
|
|
|
request.app = self
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
if request.path == '/':
|
2011-11-16 08:54:23 +00:00
|
|
|
params = urllib.urlencode(request.args)
|
2011-10-05 17:58:26 +00:00
|
|
|
return werkzeug.utils.redirect(self.root + '?' + params, 301)(
|
|
|
|
environ, start_response)
|
2011-12-13 09:28:42 +00:00
|
|
|
elif request.path == '/mobile':
|
2011-10-05 17:58:26 +00:00
|
|
|
return werkzeug.utils.redirect(
|
|
|
|
'/web_mobile/static/src/web_mobile.html', 301)(environ, start_response)
|
|
|
|
|
|
|
|
handler = self.find_handler(*(request.path.split('/')[1:]))
|
|
|
|
|
|
|
|
if not handler:
|
|
|
|
response = werkzeug.exceptions.NotFound()
|
|
|
|
else:
|
|
|
|
with session_context(request, self.session_storage, self.session_cookie) as session:
|
|
|
|
result = handler( request, self.config)
|
|
|
|
|
|
|
|
if isinstance(result, basestring):
|
|
|
|
headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
|
|
|
|
response = werkzeug.wrappers.Response(result, headers=headers)
|
|
|
|
else:
|
|
|
|
response = result
|
|
|
|
|
2011-10-13 08:13:51 +00:00
|
|
|
if hasattr(response, 'set_cookie'):
|
|
|
|
response.set_cookie(self.session_cookie, session.sid)
|
2011-10-05 17:58:26 +00:00
|
|
|
|
|
|
|
return response(environ, start_response)
|
|
|
|
|
2012-01-16 10:43:29 +00:00
|
|
|
def _load_addons(self, openerp_addons_namespace=True):
|
2011-10-05 17:58:26 +00:00
|
|
|
"""
|
|
|
|
Loads all addons at the specified addons path, returns a mapping of
|
|
|
|
static URLs to the corresponding directories
|
|
|
|
"""
|
|
|
|
statics = {}
|
|
|
|
for addons_path in self.config.addons_path:
|
|
|
|
for module in os.listdir(addons_path):
|
|
|
|
if module not in addons_module:
|
|
|
|
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
|
|
|
|
path_static = os.path.join(addons_path, module, 'static')
|
|
|
|
if os.path.isfile(manifest_path) and os.path.isdir(path_static):
|
|
|
|
manifest = ast.literal_eval(open(manifest_path).read())
|
|
|
|
manifest['addons_path'] = addons_path
|
2012-02-02 20:31:09 +00:00
|
|
|
_logger.debug("Loading %s", module)
|
2012-01-16 10:43:29 +00:00
|
|
|
if openerp_addons_namespace:
|
|
|
|
m = __import__('openerp.addons.' + module)
|
|
|
|
else:
|
|
|
|
m = __import__(module)
|
2011-10-05 17:58:26 +00:00
|
|
|
addons_module[module] = m
|
|
|
|
addons_manifest[module] = manifest
|
|
|
|
statics['/%s/static' % module] = path_static
|
2012-02-23 12:28:29 +00:00
|
|
|
for k, v in controllers_class:
|
2011-10-05 17:58:26 +00:00
|
|
|
if k not in controllers_object:
|
|
|
|
o = v()
|
|
|
|
controllers_object[k] = o
|
|
|
|
if hasattr(o, '_cp_path'):
|
|
|
|
controllers_path[o._cp_path] = o
|
|
|
|
return statics
|
|
|
|
|
|
|
|
def find_handler(self, *l):
|
|
|
|
"""
|
|
|
|
Tries to discover the controller handling the request for the path
|
|
|
|
specified by the provided parameters
|
|
|
|
|
|
|
|
:param l: path sections to a controller or controller method
|
|
|
|
:returns: a callable matching the path sections, or ``None``
|
|
|
|
:rtype: ``Controller | None``
|
|
|
|
"""
|
2012-02-01 14:30:32 +00:00
|
|
|
if l:
|
|
|
|
ps = '/' + '/'.join(l)
|
|
|
|
meth = 'index'
|
|
|
|
while ps:
|
|
|
|
c = controllers_path.get(ps)
|
|
|
|
if c:
|
2011-10-05 17:58:26 +00:00
|
|
|
m = getattr(c, meth)
|
|
|
|
if getattr(m, 'exposed', False):
|
|
|
|
_logger.debug("Dispatching to %s %s %s", ps, c, meth)
|
|
|
|
return m
|
2012-02-01 14:30:32 +00:00
|
|
|
ps, _slash, meth = ps.rpartition('/')
|
2011-10-05 17:58:26 +00:00
|
|
|
return None
|
|
|
|
|
2012-01-05 16:56:33 +00:00
|
|
|
class LibException(Exception):
|
|
|
|
""" Base of all client lib exceptions """
|
|
|
|
def __init__(self,code=None,message=None):
|
|
|
|
self.code = code
|
|
|
|
self.message = message
|
|
|
|
|
|
|
|
class ApplicationError(LibException):
|
|
|
|
""" maps to code: 1, server side: Exception or openerp.exceptions.DeferredException"""
|
|
|
|
|
|
|
|
class Warning(LibException):
|
|
|
|
""" maps to code: 2, server side: openerp.exceptions.Warning"""
|
|
|
|
|
|
|
|
class AccessError(LibException):
|
|
|
|
""" maps to code: 3, server side: openerp.exceptions.AccessError"""
|
|
|
|
|
|
|
|
class AccessDenied(LibException):
|
|
|
|
""" maps to code: 4, server side: openerp.exceptions.AccessDenied"""
|
|
|
|
|
|
|
|
|
|
|
|
class LocalConnector(openerplib.Connector):
|
|
|
|
"""
|
|
|
|
A type of connector that uses the XMLRPC protocol.
|
|
|
|
"""
|
|
|
|
PROTOCOL = 'local'
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def send(self, service_name, method, *args):
|
|
|
|
import openerp
|
|
|
|
import traceback
|
|
|
|
import xmlrpclib
|
|
|
|
try:
|
|
|
|
result = openerp.netsvc.dispatch_rpc(service_name, method, args)
|
|
|
|
except Exception,e:
|
|
|
|
# TODO change the except to raise LibException instead of their emulated xmlrpc fault
|
|
|
|
if isinstance(e, openerp.osv.osv.except_osv):
|
|
|
|
fault = xmlrpclib.Fault('warning -- ' + e.name + '\n\n' + e.value, '')
|
|
|
|
elif isinstance(e, openerp.exceptions.Warning):
|
|
|
|
fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '')
|
|
|
|
elif isinstance(e, openerp.exceptions.AccessError):
|
|
|
|
fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '')
|
|
|
|
elif isinstance(e, openerp.exceptions.AccessDenied):
|
|
|
|
fault = xmlrpclib.Fault('AccessDenied', str(e))
|
|
|
|
elif isinstance(e, openerp.exceptions.DeferredException):
|
|
|
|
info = e.traceback
|
|
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
|
|
fault = xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info)
|
|
|
|
else:
|
|
|
|
info = sys.exc_info()
|
|
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
|
|
fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info)
|
|
|
|
raise fault
|
|
|
|
return result
|
|
|
|
|