[MERGE] Sync with trunk
bzr revid: tde@openerp.com-20130624160023-lhg92flym8svgo0l
This commit is contained in:
commit
d7e5eb67bc
File diff suppressed because it is too large
Load Diff
|
@ -86,9 +86,8 @@ TESTING = Template(u"""<!DOCTYPE html>
|
|||
""", default_filters=['h'])
|
||||
|
||||
class TestRunnerController(http.Controller):
|
||||
_cp_path = '/web/tests'
|
||||
|
||||
@http.httprequest
|
||||
@http.route('/web/tests', type='http', auth="none")
|
||||
def index(self, req, mod=None, **kwargs):
|
||||
ms = module.get_modules()
|
||||
manifests = dict(
|
||||
|
|
|
@ -6,32 +6,49 @@
|
|||
Welcome to OpenERP Web's documentation!
|
||||
=======================================
|
||||
|
||||
Contents:
|
||||
Basics
|
||||
------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
module
|
||||
widget
|
||||
changelog-7.0
|
||||
|
||||
Server-Side Web Framework
|
||||
-------------------------
|
||||
|
||||
async
|
||||
rpc
|
||||
qweb
|
||||
client_action
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
web_controllers
|
||||
|
||||
Javascript
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
guidelines
|
||||
|
||||
widget
|
||||
rpc
|
||||
async
|
||||
qweb
|
||||
client_action
|
||||
testing
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
search_view
|
||||
list_view
|
||||
form_view
|
||||
|
||||
changelog-7.0
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
|
||||
Web Controllers
|
||||
===============
|
||||
|
||||
Web controllers are classes in OpenERP able to catch the http requests sent by any browser. They allow to generate
|
||||
html pages to be served like any web server, implement new methods to be used by the Javascript client, etc...
|
||||
|
||||
Controllers File
|
||||
----------------
|
||||
|
||||
By convention the controllers should be placed in the controllers directory of the module. Example:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
web_example
|
||||
├── controllers
|
||||
│ ├── __init__.py
|
||||
│ └── my_controllers.py
|
||||
├── __init__.py
|
||||
└── __openerp__.py
|
||||
|
||||
In ``__init__.py`` you must add:
|
||||
|
||||
::
|
||||
|
||||
import controllers
|
||||
|
||||
And here is the content of ``controllers/__init__.py``:
|
||||
|
||||
::
|
||||
|
||||
import my_controllers
|
||||
|
||||
Now you can put the following content in ``controllers/my_controllers.py``:
|
||||
|
||||
::
|
||||
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
|
||||
Controller Declaration
|
||||
----------------------
|
||||
|
||||
In your controllers file, you can now declare a controller this way:
|
||||
|
||||
::
|
||||
|
||||
class MyController(http.Controller):
|
||||
|
||||
@http.route('/my_url/some_html', type="http")
|
||||
def some_html(self):
|
||||
return "<h1>This is a test</h1>"
|
||||
|
||||
@http.route('/my_url/some_json', type="json")
|
||||
def some_json(self):
|
||||
return {"sample_dictionary": "This is a sample JSON dictionary"}
|
||||
|
||||
A controller must inherit from ``http.Controller``. Each time you define a method with ``@http.route()`` it defines a
|
||||
url to match. As example, the ``some_html()`` method will be called a client query the ``/my_url/some_html`` url.
|
||||
|
||||
Pure HTTP Requests
|
||||
------------------
|
||||
|
||||
You can define methods to get any normal http requests by passing ``'http'`` to the ``type`` argument of
|
||||
``http.route()``. When doing so, you get the HTTP parameters as named parameters of the method:
|
||||
|
||||
::
|
||||
|
||||
@http.route('/say_hello', type="http")
|
||||
def say_hello(self, name):
|
||||
return "<h1>Hello %s</h1>" % name
|
||||
|
||||
This url could be contacted by typing this url in a browser: ``http://localhost:8069/say_hello?name=Nicolas``.
|
||||
|
||||
JSON Requests
|
||||
-------------
|
||||
|
||||
Methods that received JSON can be defined by passing ``'json'`` to the ``type`` argument of ``http.route()``. The
|
||||
OpenERP Javascript client can contact these methods using the JSON-RPC protocol. JSON methods must return JSON. Like the
|
||||
HTTP methods they receive arguments as named parameters (except these arguments are JSON-RPC parameters).
|
||||
|
||||
::
|
||||
|
||||
@http.route('/division', type="json")
|
||||
def division(self, i, j):
|
||||
return i / j # returns a number
|
||||
|
||||
URL Patterns
|
||||
------------
|
||||
|
||||
Any URL passed to ``http.route()`` can contain patterns. Example:
|
||||
|
||||
::
|
||||
|
||||
@http.route('/files/<path:file_path>', type="http")
|
||||
def files(self, file_path):
|
||||
... # return a file identified by the path store in the 'my_path' variable
|
||||
|
||||
When such patterns are used, the method will received additional parameters that correspond to the parameters defined in
|
||||
the url. For exact documentation about url patterns, see Werkzeug's documentation:
|
||||
http://werkzeug.pocoo.org/docs/routing/ .
|
||||
|
||||
Also note you can pass multiple urls to ``http.route()``:
|
||||
|
||||
|
||||
::
|
||||
|
||||
@http.route(['/files/<path:file_path>', '/other_url/<path:file_path>'], type="http")
|
||||
def files(self, file_path):
|
||||
...
|
||||
|
||||
Contacting Models
|
||||
-----------------
|
||||
|
||||
To use the database you must access the OpenERP models. The global ``request`` object provides the necessary objects:
|
||||
|
||||
::
|
||||
|
||||
@http.route('/my_name', type="http")
|
||||
def my_name(self):
|
||||
my_user_record = request.registry.get("res.users").browse(request.cr, request.uid, request.uid)
|
||||
return "<h1>Your name is %s</h1>" % my_user_record.name
|
||||
|
||||
``request.registry`` is the registry that gives you access to the models. It is the equivalent of ``self.pool`` when
|
||||
working inside OpenERP models.
|
||||
|
||||
``request.cr`` is the cursor object. This is the ``cr`` parameter you have to pass as first argument of every model
|
||||
method in OpenERP.
|
||||
|
||||
``request.uid`` is the id of the current logged in user. This is the ``uid`` parameter you have to pass as second
|
||||
argument of every model method in OpenERP.
|
||||
|
||||
Authorization Levels
|
||||
--------------------
|
||||
|
||||
By default, all methods can only be used by users logged into OpenERP (OpenERP uses cookies to track logged users).
|
||||
There are some cases when you need to enable not-logged in users to access some methods. To do so, add the ``'db'``
|
||||
value to the ``auth`` parameter of ``http.route()``:
|
||||
|
||||
::
|
||||
|
||||
@http.route('/hello', type="http", auth="db")
|
||||
def hello(self):
|
||||
return "<div>Hello unknown user!</div>"
|
||||
|
||||
Please note the ``request.uid`` user id will be ``None`` inside this method call. This is due to the fact no user was
|
||||
authenticated.
|
||||
|
||||
Overriding Controllers
|
||||
----------------------
|
||||
|
||||
Existing routes can be overridden. To do so, create a controller that inherit the controller containing the route you
|
||||
want to override. Example that redefine the home page of your OpenERP application.
|
||||
|
||||
::
|
||||
|
||||
import openerp.addons.web.controllers.main as main
|
||||
|
||||
class Home2(main.Home):
|
||||
@http.route('/', type="http", auth="db")
|
||||
def index(self):
|
||||
return "<div>This is my new home page.</div>"
|
||||
|
||||
By re-defining the ``index()`` method, you change the behavior of the original ``Home`` class. Now the ``'/'`` route
|
||||
will match the new ``index()`` method in ``Home2``.
|
|
@ -20,6 +20,7 @@ import traceback
|
|||
import urlparse
|
||||
import uuid
|
||||
import errno
|
||||
import re
|
||||
|
||||
import babel.core
|
||||
import simplejson
|
||||
|
@ -29,10 +30,11 @@ import werkzeug.exceptions
|
|||
import werkzeug.utils
|
||||
import werkzeug.wrappers
|
||||
import werkzeug.wsgi
|
||||
import werkzeug.routing as routing
|
||||
import urllib2
|
||||
|
||||
import openerp
|
||||
|
||||
import session
|
||||
import openerp.service.security as security
|
||||
|
||||
import inspect
|
||||
import functools
|
||||
|
@ -82,21 +84,48 @@ class WebRequest(object):
|
|||
.. attribute:: debug
|
||||
|
||||
``bool``, indicates whether the debug mode is active on the client
|
||||
|
||||
.. attribute:: db
|
||||
|
||||
``str``, the name of the database linked to the current request. Can be ``None``
|
||||
if the current request uses the ``none`` authentication.
|
||||
|
||||
.. attribute:: uid
|
||||
|
||||
``int``, the id of the user related to the current request. Can be ``None``
|
||||
if the current request uses the ``none`` or the ``db`` authenticatoin.
|
||||
"""
|
||||
def __init__(self, request):
|
||||
self.httprequest = request
|
||||
def __init__(self, httprequest):
|
||||
self.httprequest = httprequest
|
||||
self.httpresponse = None
|
||||
self.httpsession = request.session
|
||||
self.httpsession = httprequest.session
|
||||
self.db = None
|
||||
self.uid = None
|
||||
self.func = None
|
||||
self.auth_method = None
|
||||
self._cr_cm = None
|
||||
self._cr = None
|
||||
self.func_request_type = None
|
||||
|
||||
def init(self, params):
|
||||
self.params = dict(params)
|
||||
# OpenERP session setup
|
||||
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
|
||||
self.session_id = self.params.pop("session_id", None)
|
||||
if not self.session_id:
|
||||
i0 = self.httprequest.cookies.get("instance0|session_id", None)
|
||||
if i0:
|
||||
self.session_id = simplejson.loads(urllib2.unquote(i0))
|
||||
else:
|
||||
self.session_id = uuid.uuid4().hex
|
||||
self.session = self.httpsession.get(self.session_id)
|
||||
if not self.session:
|
||||
self.session = session.OpenERPSession()
|
||||
self.session = OpenERPSession()
|
||||
self.httpsession[self.session_id] = self.session
|
||||
|
||||
with set_request(self):
|
||||
self.db = self.session._db or db_monodb()
|
||||
|
||||
# TODO: remove this
|
||||
# set db/uid trackers - they're cleaned up at the WSGI
|
||||
# dispatching phase in openerp.service.wsgi_server.application
|
||||
if self.session._db:
|
||||
|
@ -121,12 +150,100 @@ class WebRequest(object):
|
|||
# we use _ as seprator where RFC2616 uses '-'
|
||||
self.lang = lang.replace('-', '_')
|
||||
|
||||
@contextlib.contextmanager
|
||||
def registry_cr(self):
|
||||
dbname = self.session._db or openerp.addons.web.controllers.main.db_monodb(self)
|
||||
registry = openerp.modules.registry.RegistryManager.get(dbname.lower())
|
||||
with registry.cursor() as cr:
|
||||
yield (registry, cr)
|
||||
def _authenticate(self):
|
||||
if self.auth_method == "none":
|
||||
self.db = None
|
||||
self.uid = None
|
||||
elif self.auth_method == "db":
|
||||
self.db = self.session._db or db_monodb()
|
||||
if not self.db:
|
||||
raise SessionExpiredException("No valid database for request %s" % self.httprequest)
|
||||
self.uid = None
|
||||
else: # auth
|
||||
try:
|
||||
self.session.check_security()
|
||||
except SessionExpiredException, e:
|
||||
raise SessionExpiredException("Session expired for request %s" % self.httprequest)
|
||||
self.db = self.session._db
|
||||
self.uid = self.session._uid
|
||||
|
||||
@property
|
||||
def registry(self):
|
||||
"""
|
||||
The registry to the database linked to this request. Can be ``None`` if the current request uses the
|
||||
``none'' authentication.
|
||||
"""
|
||||
return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
|
||||
|
||||
@property
|
||||
def cr(self):
|
||||
"""
|
||||
The cursor initialized for the current method call. If the current request uses the ``none`` authentication
|
||||
trying to access this property will raise an exception.
|
||||
"""
|
||||
# some magic to lazy create the cr
|
||||
if not self._cr_cm:
|
||||
self._cr_cm = self.registry.cursor()
|
||||
self._cr = self._cr_cm.__enter__()
|
||||
return self._cr
|
||||
|
||||
def _call_function(self, *args, **kwargs):
|
||||
self._authenticate()
|
||||
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))
|
||||
return self.func(*args, **kwargs)
|
||||
finally:
|
||||
# just to be sure no one tries to re-use the request
|
||||
self.db = None
|
||||
self.uid = None
|
||||
|
||||
def route(route, type="http", auth="user"):
|
||||
"""
|
||||
Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
|
||||
of ``Controller``.
|
||||
|
||||
Decorator to put on a controller method to inform it does not require a user to be logged. When this decorator
|
||||
is used, ``request.uid`` will be ``None``. The request will still try to detect the database and an exception
|
||||
will be launched if there is no way to guess it.
|
||||
|
||||
:param route: string or array. The route part that will determine which http requests will match the decorated
|
||||
method. Can be a single string or an array of strings. See werkzeug's routing documentation for the format of
|
||||
route expression ( http://werkzeug.pocoo.org/docs/routing/ ).
|
||||
:param type: The type of request, can be ``'http'`` or ``'json'``.
|
||||
:param auth: The type of authentication method, can on of the following:
|
||||
|
||||
* ``auth``: The user must be authenticated.
|
||||
* ``db``: There is no need for the user to be authenticated but there must be a way to find the current
|
||||
database.
|
||||
* ``none``: The method is always active, even if there is no database. Mainly used by the framework and
|
||||
authentication modules.
|
||||
"""
|
||||
assert type in ["http", "json"]
|
||||
assert auth in ["user", "db", "none"]
|
||||
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
|
||||
return decorator
|
||||
|
||||
def reject_nonliteral(dct):
|
||||
if '__ref' in dct:
|
||||
|
@ -167,26 +284,30 @@ class JsonRequest(WebRequest):
|
|||
"id": null}
|
||||
|
||||
"""
|
||||
def dispatch(self, method):
|
||||
""" Calls the method asked for by the JSON-RPC2 or JSONP request
|
||||
_request_type = "json"
|
||||
|
||||
:param method: the method which received the request
|
||||
def __init__(self, *args):
|
||||
super(JsonRequest, self).__init__(*args)
|
||||
|
||||
self.jsonp_handler = None
|
||||
|
||||
:returns: an utf8 encoded JSON-RPC2 or JSONP reply
|
||||
"""
|
||||
args = self.httprequest.args
|
||||
jsonp = args.get('jsonp')
|
||||
requestf = None
|
||||
self.jsonp = jsonp
|
||||
request = None
|
||||
request_id = args.get('id')
|
||||
|
||||
if jsonp and self.httprequest.method == 'POST':
|
||||
# jsonp 2 steps step1 POST: save call
|
||||
self.init(args)
|
||||
self.session.jsonp_requests[request_id] = self.httprequest.form['r']
|
||||
headers=[('Content-Type', 'text/plain; charset=utf-8')]
|
||||
r = werkzeug.wrappers.Response(request_id, headers=headers)
|
||||
return r
|
||||
|
||||
def handler():
|
||||
self.session.jsonp_requests[request_id] = self.httprequest.form['r']
|
||||
headers=[('Content-Type', 'text/plain; charset=utf-8')]
|
||||
r = werkzeug.wrappers.Response(request_id, headers=headers)
|
||||
return r
|
||||
self.jsonp_handler = handler
|
||||
return
|
||||
elif jsonp and args.get('r'):
|
||||
# jsonp method GET
|
||||
request = args.get('r')
|
||||
|
@ -196,22 +317,28 @@ class JsonRequest(WebRequest):
|
|||
request = self.session.jsonp_requests.pop(request_id, "")
|
||||
else:
|
||||
# regular jsonrpc2
|
||||
requestf = self.httprequest.stream
|
||||
request = self.httprequest.stream.read()
|
||||
|
||||
# Read POST content or POST Form Data named "request"
|
||||
self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
|
||||
self.init(self.jsonrequest.get("params", {}))
|
||||
|
||||
def dispatch(self):
|
||||
""" Calls the method asked for by the JSON-RPC2 or JSONP request
|
||||
|
||||
:returns: an utf8 encoded JSON-RPC2 or JSONP reply
|
||||
"""
|
||||
if self.jsonp_handler:
|
||||
return self.jsonp_handler()
|
||||
response = {"jsonrpc": "2.0" }
|
||||
error = None
|
||||
try:
|
||||
# Read POST content or POST Form Data named "request"
|
||||
if requestf:
|
||||
self.jsonrequest = simplejson.load(requestf, object_hook=reject_nonliteral)
|
||||
else:
|
||||
self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
|
||||
self.init(self.jsonrequest.get("params", {}))
|
||||
#if _logger.isEnabledFor(logging.DEBUG):
|
||||
# _logger.debug("--> %s.%s\n%s", method.im_class.__name__, method.__name__, pprint.pformat(self.jsonrequest))
|
||||
# _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest))
|
||||
response['id'] = self.jsonrequest.get('id')
|
||||
response["result"] = method(**self.params)
|
||||
except session.AuthenticationError, e:
|
||||
response["result"] = self._call_function(**self.params)
|
||||
except AuthenticationError, e:
|
||||
_logger.exception("Exception during JSON request handling.")
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 100,
|
||||
|
@ -219,6 +346,7 @@ class JsonRequest(WebRequest):
|
|||
'data': se
|
||||
}
|
||||
except Exception, e:
|
||||
_logger.exception("Exception during JSON request handling.")
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
|
@ -231,13 +359,13 @@ class JsonRequest(WebRequest):
|
|||
if _logger.isEnabledFor(logging.DEBUG):
|
||||
_logger.debug("<--\n%s", pprint.pformat(response))
|
||||
|
||||
if jsonp:
|
||||
if self.jsonp:
|
||||
# If we use jsonp, that's mean we are called from another host
|
||||
# Some browser (IE and Safari) do no allow third party cookies
|
||||
# We need then to manage http sessions manually.
|
||||
response['httpsessionid'] = self.httpsession.sid
|
||||
mime = 'application/javascript'
|
||||
body = "%s(%s);" % (jsonp, simplejson.dumps(response),)
|
||||
body = "%s(%s);" % (self.jsonp, simplejson.dumps(response),)
|
||||
else:
|
||||
mime = 'application/json'
|
||||
body = simplejson.dumps(response)
|
||||
|
@ -285,26 +413,34 @@ def jsonrequest(f):
|
|||
the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
|
||||
beforehand)
|
||||
"""
|
||||
f.exposed = 'json'
|
||||
return f
|
||||
f.combine = True
|
||||
base = f.__name__
|
||||
if f.__name__ == "index":
|
||||
base = ""
|
||||
return route([base, os.path.join(base, "<path:_ignored_path>")], type="json", auth="user")(f)
|
||||
|
||||
class HttpRequest(WebRequest):
|
||||
""" Regular GET/POST request
|
||||
"""
|
||||
def dispatch(self, method):
|
||||
_request_type = "http"
|
||||
|
||||
def __init__(self, *args):
|
||||
super(HttpRequest, self).__init__(*args)
|
||||
params = dict(self.httprequest.args)
|
||||
params.update(self.httprequest.form)
|
||||
params.update(self.httprequest.files)
|
||||
self.init(params)
|
||||
|
||||
def dispatch(self):
|
||||
akw = {}
|
||||
for key, value in self.httprequest.args.iteritems():
|
||||
if isinstance(value, basestring) and len(value) < 1024:
|
||||
akw[key] = value
|
||||
else:
|
||||
akw[key] = type(value)
|
||||
#_logger.debug("%s --> %s.%s %r", self.httprequest.method, method.im_class.__name__, method.__name__, akw)
|
||||
#_logger.debug("%s --> %s.%s %r", self.httprequest.func, func.im_class.__name__, func.__name__, akw)
|
||||
try:
|
||||
r = method(**self.params)
|
||||
r = self._call_function(**self.params)
|
||||
except werkzeug.exceptions.HTTPException, e:
|
||||
r = e
|
||||
except Exception, e:
|
||||
|
@ -360,8 +496,11 @@ def httprequest(f):
|
|||
merged in the same dictionary), apart from the ``session_id``, ``context``
|
||||
and ``debug`` keys (which are stripped out beforehand)
|
||||
"""
|
||||
f.exposed = 'http'
|
||||
return f
|
||||
f.combine = True
|
||||
base = f.__name__
|
||||
if f.__name__ == "index":
|
||||
base = ""
|
||||
return route([base, os.path.join(base, "<path:_ignored_path>")], type="http", auth="user")(f)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Local storage of requests
|
||||
|
@ -378,6 +517,9 @@ def set_request(request):
|
|||
_request_stack.pop()
|
||||
return with_obj()
|
||||
|
||||
"""
|
||||
A global proxy that always redirect to the current request object.
|
||||
"""
|
||||
request = _request_stack()
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
@ -385,11 +527,7 @@ request = _request_stack()
|
|||
#----------------------------------------------------------
|
||||
addons_module = {}
|
||||
addons_manifest = {}
|
||||
controllers_class = []
|
||||
controllers_class_path = {}
|
||||
controllers_object = {}
|
||||
controllers_object_path = {}
|
||||
controllers_path = {}
|
||||
controllers_per_module = {}
|
||||
|
||||
class ControllerType(type):
|
||||
def __init__(cls, name, bases, attrs):
|
||||
|
@ -408,47 +546,268 @@ class ControllerType(type):
|
|||
|
||||
# store the controller in the controllers list
|
||||
name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls)
|
||||
controllers_class.append(name_class)
|
||||
path = attrs.get('_cp_path')
|
||||
if path not in controllers_class_path:
|
||||
controllers_class_path[path] = name_class
|
||||
class_path = name_class[0].split(".")
|
||||
if not class_path[:2] == ["openerp", "addons"]:
|
||||
return
|
||||
# we want to know all modules that have controllers
|
||||
module = class_path[2]
|
||||
controllers_per_module.setdefault(module, [])
|
||||
# but we only store controllers directly inheriting from Controller
|
||||
if not "Controller" in globals() or not Controller in bases:
|
||||
return
|
||||
controllers_per_module.setdefault(module, []).append(name_class)
|
||||
|
||||
class Controller(object):
|
||||
__metaclass__ = ControllerType
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
subclasses = [c for c in cls.__subclasses__() if c._cp_path == cls._cp_path]
|
||||
if subclasses:
|
||||
name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
|
||||
cls = type(name, tuple(reversed(subclasses)), {})
|
||||
|
||||
return object.__new__(cls)
|
||||
|
||||
def get_wrapped_method(self, name):
|
||||
if name in self.__class__._methods_wrapper:
|
||||
return functools.partial(self.__class__._methods_wrapper[name], self)
|
||||
else:
|
||||
return getattr(self, name)
|
||||
|
||||
#############################
|
||||
# OpenERP Sessions #
|
||||
#############################
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
pass
|
||||
|
||||
class SessionExpiredException(Exception):
|
||||
pass
|
||||
|
||||
class Service(object):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use ``openerp.netsvc.dispatch_rpc()`` instead.
|
||||
"""
|
||||
def __init__(self, session, service_name):
|
||||
self.session = session
|
||||
self.service_name = service_name
|
||||
|
||||
def __getattr__(self, method):
|
||||
def proxy_method(*args):
|
||||
result = openerp.netsvc.dispatch_rpc(self.service_name, method, args)
|
||||
return result
|
||||
return proxy_method
|
||||
|
||||
class Model(object):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
|
||||
"""
|
||||
def __init__(self, session, model):
|
||||
self.session = session
|
||||
self.model = model
|
||||
self.proxy = self.session.proxy('object')
|
||||
|
||||
def __getattr__(self, method):
|
||||
self.session.assert_valid()
|
||||
def proxy(*args, **kw):
|
||||
# Can't provide any retro-compatibility for this case, so we check it and raise an Exception
|
||||
# to tell the programmer to adapt his code
|
||||
if not request.db or not request.uid or self.session._db != request.db \
|
||||
or self.session._uid != request.uid:
|
||||
raise Exception("Trying to use Model with badly configured database or user.")
|
||||
|
||||
mod = request.registry.get(self.model)
|
||||
meth = getattr(mod, method)
|
||||
cr = request.cr
|
||||
result = meth(cr, request.uid, *args, **kw)
|
||||
# reorder read
|
||||
if method == "read":
|
||||
if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
|
||||
index = {}
|
||||
for r in result:
|
||||
index[r['id']] = r
|
||||
result = [index[x] for x in args[0] if x in index]
|
||||
return result
|
||||
return proxy
|
||||
|
||||
class OpenERPSession(object):
|
||||
"""
|
||||
An OpenERP RPC session, a given user can own multiple such sessions
|
||||
in a web session.
|
||||
|
||||
.. attribute:: context
|
||||
|
||||
The session context, a ``dict``. Can be reloaded by calling
|
||||
:meth:`openerpweb.openerpweb.OpenERPSession.get_context`
|
||||
|
||||
.. attribute:: domains_store
|
||||
|
||||
A ``dict`` matching domain keys to evaluable (but non-literal) domains.
|
||||
|
||||
Used to store references to non-literal domains which need to be
|
||||
round-tripped to the client browser.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._creation_time = time.time()
|
||||
self._db = False
|
||||
self._uid = False
|
||||
self._login = False
|
||||
self._password = False
|
||||
self._suicide = False
|
||||
self.context = {}
|
||||
self.jsonp_requests = {} # FIXME use a LRU
|
||||
|
||||
def authenticate(self, db, login=None, password=None, env=None, uid=None):
|
||||
"""
|
||||
Authenticate the current user with the given db, login and password. If successful, store
|
||||
the authentication parameters in the current session and request.
|
||||
|
||||
:param uid: If not None, that user id will be used instead the login to authenticate the user.
|
||||
"""
|
||||
if uid is None:
|
||||
uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
|
||||
else:
|
||||
security.check(db, uid, password)
|
||||
self._db = db
|
||||
self._uid = uid
|
||||
self._login = login
|
||||
self._password = password
|
||||
request.db = db
|
||||
request.uid = uid
|
||||
|
||||
if uid: self.get_context()
|
||||
return uid
|
||||
|
||||
def check_security(self):
|
||||
"""
|
||||
Chech the current authentication parameters to know if those are still valid. This method
|
||||
should be called at each request. If the authentication fails, a ``SessionExpiredException``
|
||||
is raised.
|
||||
"""
|
||||
if not self._db or not self._uid:
|
||||
raise SessionExpiredException("Session expired")
|
||||
security.check(self._db, self._uid, self._password)
|
||||
|
||||
def get_context(self):
|
||||
"""
|
||||
Re-initializes the current user's session context (based on
|
||||
his preferences) by calling res.users.get_context() with the old
|
||||
context.
|
||||
|
||||
:returns: the new context
|
||||
"""
|
||||
assert self._uid, "The user needs to be logged-in to initialize his context"
|
||||
self.context = request.registry.get('res.users').context_get(request.cr, request.uid) or {}
|
||||
self.context['uid'] = self._uid
|
||||
self._fix_lang(self.context)
|
||||
return self.context
|
||||
|
||||
def _fix_lang(self, context):
|
||||
""" OpenERP provides languages which may not make sense and/or may not
|
||||
be understood by the web client's libraries.
|
||||
|
||||
Fix those here.
|
||||
|
||||
:param dict context: context to fix
|
||||
"""
|
||||
lang = context['lang']
|
||||
|
||||
# inane OpenERP locale
|
||||
if lang == 'ar_AR':
|
||||
lang = 'ar'
|
||||
|
||||
# lang to lang_REGION (datejs only handles lang_REGION, no bare langs)
|
||||
if lang in babel.core.LOCALE_ALIASES:
|
||||
lang = babel.core.LOCALE_ALIASES[lang]
|
||||
|
||||
context['lang'] = lang or 'en_US'
|
||||
|
||||
def send(self, service_name, method, *args):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use ``openerp.netsvc.dispatch_rpc()`` instead.
|
||||
"""
|
||||
return openerp.netsvc.dispatch_rpc(service_name, method, args)
|
||||
|
||||
def proxy(self, service):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use ``openerp.netsvc.dispatch_rpc()`` instead.
|
||||
"""
|
||||
return Service(self, service)
|
||||
|
||||
def assert_valid(self, force=False):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use ``check_security()`` instead.
|
||||
|
||||
Ensures this session is valid (logged into the openerp server)
|
||||
"""
|
||||
if self._uid and not force:
|
||||
return
|
||||
# TODO use authenticate instead of login
|
||||
self._uid = self.proxy("common").login(self._db, self._login, self._password)
|
||||
if not self._uid:
|
||||
raise AuthenticationError("Authentication failure")
|
||||
|
||||
def ensure_valid(self):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use ``check_security()`` instead.
|
||||
"""
|
||||
if self._uid:
|
||||
try:
|
||||
self.assert_valid(True)
|
||||
except Exception:
|
||||
self._uid = None
|
||||
|
||||
def execute(self, model, func, *l, **d):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
|
||||
"""
|
||||
model = self.model(model)
|
||||
r = getattr(model, func)(*l, **d)
|
||||
return r
|
||||
|
||||
def exec_workflow(self, model, id, signal):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
|
||||
"""
|
||||
self.assert_valid()
|
||||
r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id)
|
||||
return r
|
||||
|
||||
def model(self, model):
|
||||
"""
|
||||
.. deprecated:: 8.0
|
||||
Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
|
||||
|
||||
Get an RPC proxy for the object ``model``, bound to this session.
|
||||
|
||||
:param model: an OpenERP model name
|
||||
:type model: str
|
||||
:rtype: a model object
|
||||
"""
|
||||
if self._db == False:
|
||||
raise SessionExpiredException("Session expired")
|
||||
|
||||
return Model(self, model)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Session context manager
|
||||
#----------------------------------------------------------
|
||||
@contextlib.contextmanager
|
||||
def session_context(request, session_store, session_lock, sid):
|
||||
def session_context(httprequest, session_store, session_lock, sid):
|
||||
with session_lock:
|
||||
if sid:
|
||||
request.session = session_store.get(sid)
|
||||
httprequest.session = session_store.get(sid)
|
||||
else:
|
||||
request.session = session_store.new()
|
||||
httprequest.session = session_store.new()
|
||||
try:
|
||||
yield request.session
|
||||
yield httprequest.session
|
||||
finally:
|
||||
# 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
|
||||
removed_sessions = set()
|
||||
for key, value in request.session.items():
|
||||
if not isinstance(value, session.OpenERPSession):
|
||||
for key, value in httprequest.session.items():
|
||||
if not isinstance(value, OpenERPSession):
|
||||
continue
|
||||
if getattr(value, '_suicide', False) or (
|
||||
not value._uid
|
||||
|
@ -457,7 +816,7 @@ def session_context(request, session_store, session_lock, sid):
|
|||
and value._creation_time + (60*5) < time.time()):
|
||||
_logger.debug('remove session %s', key)
|
||||
removed_sessions.add(key)
|
||||
del request.session[key]
|
||||
del httprequest.session[key]
|
||||
|
||||
with session_lock:
|
||||
if sid:
|
||||
|
@ -474,9 +833,9 @@ def session_context(request, session_store, session_lock, sid):
|
|||
# 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():
|
||||
for k, v in httprequest.session.iteritems():
|
||||
stored = in_store.get(k)
|
||||
if stored and isinstance(v, session.OpenERPSession):
|
||||
if stored and isinstance(v, OpenERPSession):
|
||||
if hasattr(v, 'contexts_store'):
|
||||
del v.contexts_store
|
||||
if hasattr(v, 'domains_store'):
|
||||
|
@ -488,10 +847,10 @@ def session_context(request, session_store, session_lock, sid):
|
|||
|
||||
# add missing keys
|
||||
for k, v in in_store.iteritems():
|
||||
if k not in request.session and k not in removed_sessions:
|
||||
request.session[k] = v
|
||||
if k not in httprequest.session and k not in removed_sessions:
|
||||
httprequest.session[k] = v
|
||||
|
||||
session_store.save(request.session)
|
||||
session_store.save(httprequest.session)
|
||||
|
||||
def session_gc(session_store):
|
||||
if random.random() < 0.001:
|
||||
|
@ -563,6 +922,9 @@ class Root(object):
|
|||
self.addons = {}
|
||||
self.statics = {}
|
||||
|
||||
self.db_routers = {}
|
||||
self.db_routers_lock = threading.Lock()
|
||||
|
||||
self.load_addons()
|
||||
|
||||
# Setup http sessions
|
||||
|
@ -571,6 +933,7 @@ class Root(object):
|
|||
self.session_lock = threading.Lock()
|
||||
_logger.debug('HTTP sessions stored in: %s', path)
|
||||
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
""" Handle a WSGI request
|
||||
"""
|
||||
|
@ -583,23 +946,33 @@ class Root(object):
|
|||
|
||||
Call the object directly.
|
||||
"""
|
||||
request = werkzeug.wrappers.Request(environ)
|
||||
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
|
||||
request.app = self
|
||||
try:
|
||||
httprequest = werkzeug.wrappers.Request(environ)
|
||||
httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict
|
||||
httprequest.app = self
|
||||
|
||||
handler = self.find_handler(*(request.path.split('/')[1:]))
|
||||
|
||||
if not handler:
|
||||
response = werkzeug.exceptions.NotFound()
|
||||
else:
|
||||
sid = request.cookies.get('sid')
|
||||
sid = httprequest.cookies.get('sid')
|
||||
if not sid:
|
||||
sid = request.args.get('sid')
|
||||
sid = httprequest.args.get('sid')
|
||||
|
||||
session_gc(self.session_store)
|
||||
|
||||
with session_context(request, self.session_store, self.session_lock, sid) as session:
|
||||
result = handler(request)
|
||||
with session_context(httprequest, self.session_store, self.session_lock, sid) as session:
|
||||
request = self._build_request(httprequest)
|
||||
db = request.db
|
||||
|
||||
if db:
|
||||
updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db)
|
||||
if updated:
|
||||
with self.db_routers_lock:
|
||||
del self.db_routers[db]
|
||||
|
||||
with set_request(request):
|
||||
self.find_handler()
|
||||
result = request.dispatch()
|
||||
|
||||
if db:
|
||||
openerp.modules.registry.RegistryManager.signal_caches_change(db)
|
||||
|
||||
if isinstance(result, basestring):
|
||||
headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
|
||||
|
@ -610,7 +983,22 @@ class Root(object):
|
|||
if hasattr(response, 'set_cookie'):
|
||||
response.set_cookie('sid', session.sid)
|
||||
|
||||
return response(environ, start_response)
|
||||
return response(environ, start_response)
|
||||
except werkzeug.exceptions.HTTPException, e:
|
||||
return e(environ, start_response)
|
||||
|
||||
def _build_request(self, httprequest):
|
||||
if httprequest.args.get('jsonp'):
|
||||
return JsonRequest(httprequest)
|
||||
|
||||
content = httprequest.stream.read()
|
||||
import cStringIO
|
||||
httprequest.stream = cStringIO.StringIO(content)
|
||||
try:
|
||||
simplejson.loads(content)
|
||||
return JsonRequest(httprequest)
|
||||
except:
|
||||
return HttpRequest(httprequest)
|
||||
|
||||
def load_addons(self):
|
||||
""" Load all addons from addons patch containg static files and
|
||||
|
@ -633,57 +1021,129 @@ class Root(object):
|
|||
addons_manifest[module] = manifest
|
||||
self.statics['/%s/static' % module] = path_static
|
||||
|
||||
for k, v in controllers_class_path.items():
|
||||
if k not in controllers_object_path and hasattr(v[1], '_cp_path'):
|
||||
o = v[1]()
|
||||
controllers_object[v[0]] = o
|
||||
controllers_object_path[k] = o
|
||||
if hasattr(o, '_cp_path'):
|
||||
controllers_path[o._cp_path] = o
|
||||
|
||||
app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics)
|
||||
self.dispatch = DisableCacheMiddleware(app)
|
||||
|
||||
def find_handler(self, *l):
|
||||
def _build_router(self, db):
|
||||
_logger.info("Generating routing configuration for database %s" % db)
|
||||
routing_map = routing.Map()
|
||||
|
||||
def gen(modules, nodb_only):
|
||||
for module in modules:
|
||||
for v in controllers_per_module[module]:
|
||||
cls = v[1]
|
||||
|
||||
subclasses = cls.__subclasses__()
|
||||
subclasses = [c for c in subclasses if c.__module__.split(".")[:2] == ["openerp", "addons"] and \
|
||||
cls.__module__.split(".")[2] in modules]
|
||||
if subclasses:
|
||||
name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
|
||||
cls = type(name, tuple(reversed(subclasses)), {})
|
||||
|
||||
o = cls()
|
||||
members = inspect.getmembers(o)
|
||||
for mk, mv in members:
|
||||
if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and \
|
||||
nodb_only == (getattr(mv, "auth", None) == "none"):
|
||||
function = (o.get_wrapped_method(mk), mv)
|
||||
for url in mv.routes:
|
||||
if getattr(mv, "combine", False):
|
||||
url = os.path.join(o._cp_path, url)
|
||||
if url.endswith("/") and len(url) > 1:
|
||||
url = url[: -1]
|
||||
routing_map.add(routing.Rule(url, endpoint=function))
|
||||
|
||||
modules_set = set(controllers_per_module.keys())
|
||||
modules_set -= set("web")
|
||||
# building all none methods
|
||||
gen(["web"] + sorted(modules_set), True)
|
||||
if not db:
|
||||
return routing_map
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
with registry.cursor() as cr:
|
||||
m = registry.get('ir.module.module')
|
||||
ids = m.search(cr, openerp.SUPERUSER_ID, [('state','=','installed')])
|
||||
installed = set([x['name'] for x in m.read(cr, 1, ids, ['name'])])
|
||||
modules_set = modules_set.intersection(set(installed))
|
||||
modules = ["web"] + sorted(modules_set)
|
||||
# building all other methods
|
||||
gen(["web"] + sorted(modules_set), False)
|
||||
|
||||
return routing_map
|
||||
|
||||
def get_db_router(self, db):
|
||||
with self.db_routers_lock:
|
||||
router = self.db_routers.get(db)
|
||||
if not router:
|
||||
router = self._build_router(db)
|
||||
with self.db_routers_lock:
|
||||
self.db_routers[db] = router
|
||||
return router
|
||||
|
||||
def find_handler(self):
|
||||
"""
|
||||
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``
|
||||
:param path: path to match
|
||||
:returns: a callable matching the path sections
|
||||
:rtype: ``Controller | None``
|
||||
"""
|
||||
if l:
|
||||
ps = '/' + '/'.join(filter(None, l))
|
||||
method_name = 'index'
|
||||
while ps:
|
||||
c = controllers_path.get(ps)
|
||||
if c:
|
||||
method = getattr(c, method_name, None)
|
||||
if method:
|
||||
exposed = getattr(method, 'exposed', False)
|
||||
method = c.get_wrapped_method(method_name)
|
||||
if exposed == 'json':
|
||||
_logger.debug("Dispatch json to %s %s %s", ps, c, method_name)
|
||||
def fct(_request):
|
||||
_req = JsonRequest(_request)
|
||||
with set_request(_req):
|
||||
return request.dispatch(method)
|
||||
return fct
|
||||
elif exposed == 'http':
|
||||
_logger.debug("Dispatch http to %s %s %s", ps, c, method_name)
|
||||
def fct(_request):
|
||||
_req = HttpRequest(_request)
|
||||
with set_request(_req):
|
||||
return request.dispatch(method)
|
||||
return fct
|
||||
if method_name != "index":
|
||||
method_name = "index"
|
||||
continue
|
||||
ps, _slash, method_name = ps.rpartition('/')
|
||||
if not ps and method_name:
|
||||
ps = '/'
|
||||
return None
|
||||
path = request.httprequest.path
|
||||
urls = self.get_db_router(request.db).bind("")
|
||||
matched, arguments = urls.match(path)
|
||||
arguments = dict([(k, v) for k, v in arguments.items() if not k.startswith("_ignored_")])
|
||||
func, original = matched
|
||||
|
||||
def nfunc(*args, **kwargs):
|
||||
kwargs.update(arguments)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
request.func = nfunc
|
||||
request.auth_method = getattr(original, "auth", "user")
|
||||
request.func_request_type = original.exposed
|
||||
|
||||
def db_list(force=False):
|
||||
proxy = request.session.proxy("db")
|
||||
dbs = proxy.list(force)
|
||||
h = request.httprequest.environ['HTTP_HOST'].split(':')[0]
|
||||
d = h.split('.')[0]
|
||||
r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
|
||||
dbs = [i for i in dbs if re.match(r, i)]
|
||||
return dbs
|
||||
|
||||
def db_monodb():
|
||||
db = None
|
||||
|
||||
# 1 try the db in the url
|
||||
db_url = request.params.get('db')
|
||||
if db_url:
|
||||
return db_url
|
||||
|
||||
try:
|
||||
dbs = db_list()
|
||||
except Exception:
|
||||
# ignore access denied
|
||||
dbs = []
|
||||
|
||||
# 2 use the database from the cookie if it's listable and still listed
|
||||
cookie_db = request.httprequest.cookies.get('last_used_database')
|
||||
if cookie_db in dbs:
|
||||
db = cookie_db
|
||||
|
||||
# 3 use the first db
|
||||
if dbs and not db:
|
||||
db = dbs[0]
|
||||
return db.lower() if db else db
|
||||
|
||||
|
||||
class JsonRpcController(Controller):
|
||||
|
||||
@route('/jsonrpc', type='json', auth="none")
|
||||
def jsonrpc(self, service, method, args):
|
||||
""" Method used by client APIs to contact OpenERP. """
|
||||
return openerp.netsvc.dispatch_rpc(service, method, args)
|
||||
|
||||
def wsgi_postload():
|
||||
openerp.wsgi.register_wsgi_handler(Root())
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
import datetime
|
||||
import babel
|
||||
import dateutil.relativedelta
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import openerp
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERPSession RPC openerp backend access
|
||||
#----------------------------------------------------------
|
||||
class AuthenticationError(Exception):
|
||||
pass
|
||||
|
||||
class SessionExpiredException(Exception):
|
||||
pass
|
||||
|
||||
class Service(object):
|
||||
def __init__(self, session, service_name):
|
||||
self.session = session
|
||||
self.service_name = service_name
|
||||
|
||||
def __getattr__(self, method):
|
||||
def proxy_method(*args):
|
||||
result = self.session.send(self.service_name, method, *args)
|
||||
return result
|
||||
return proxy_method
|
||||
|
||||
class Model(object):
|
||||
def __init__(self, session, model):
|
||||
self.session = session
|
||||
self.model = model
|
||||
self.proxy = self.session.proxy('object')
|
||||
|
||||
def __getattr__(self, method):
|
||||
self.session.assert_valid()
|
||||
def proxy(*args, **kw):
|
||||
result = self.proxy.execute_kw(self.session._db, self.session._uid, self.session._password, self.model, method, args, kw)
|
||||
# reorder read
|
||||
if method == "read":
|
||||
if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
|
||||
index = {}
|
||||
for r in result:
|
||||
index[r['id']] = r
|
||||
result = [index[x] for x in args[0] if x in index]
|
||||
return result
|
||||
return proxy
|
||||
|
||||
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
|
||||
record_ids = self.search(domain or [], offset, limit or False, order or False, context or {})
|
||||
if not record_ids: return []
|
||||
records = self.read(record_ids, fields or [], context or {})
|
||||
return records
|
||||
|
||||
class OpenERPSession(object):
|
||||
"""
|
||||
An OpenERP RPC session, a given user can own multiple such sessions
|
||||
in a web session.
|
||||
|
||||
.. attribute:: context
|
||||
|
||||
The session context, a ``dict``. Can be reloaded by calling
|
||||
:meth:`openerpweb.openerpweb.OpenERPSession.get_context`
|
||||
|
||||
.. attribute:: domains_store
|
||||
|
||||
A ``dict`` matching domain keys to evaluable (but non-literal) domains.
|
||||
|
||||
Used to store references to non-literal domains which need to be
|
||||
round-tripped to the client browser.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._creation_time = time.time()
|
||||
self._db = False
|
||||
self._uid = False
|
||||
self._login = False
|
||||
self._password = False
|
||||
self._suicide = False
|
||||
self.context = {}
|
||||
self.jsonp_requests = {} # FIXME use a LRU
|
||||
|
||||
def send(self, service_name, method, *args):
|
||||
return openerp.netsvc.dispatch_rpc(service_name, method, args)
|
||||
|
||||
def proxy(self, service):
|
||||
return Service(self, service)
|
||||
|
||||
def bind(self, db, uid, login, password):
|
||||
self._db = db
|
||||
self._uid = uid
|
||||
self._login = login
|
||||
self._password = password
|
||||
|
||||
def authenticate(self, db, login, password, env=None):
|
||||
uid = self.proxy('common').authenticate(db, login, password, env)
|
||||
self.bind(db, uid, login, password)
|
||||
|
||||
if uid: self.get_context()
|
||||
return uid
|
||||
|
||||
def assert_valid(self, force=False):
|
||||
"""
|
||||
Ensures this session is valid (logged into the openerp server)
|
||||
"""
|
||||
if self._uid and not force:
|
||||
return
|
||||
# TODO use authenticate instead of login
|
||||
self._uid = self.proxy("common").login(self._db, self._login, self._password)
|
||||
if not self._uid:
|
||||
raise AuthenticationError("Authentication failure")
|
||||
|
||||
def ensure_valid(self):
|
||||
if self._uid:
|
||||
try:
|
||||
self.assert_valid(True)
|
||||
except Exception:
|
||||
self._uid = None
|
||||
|
||||
def execute(self, model, func, *l, **d):
|
||||
model = self.model(model)
|
||||
r = getattr(model, func)(*l, **d)
|
||||
return r
|
||||
|
||||
def exec_workflow(self, model, id, signal):
|
||||
self.assert_valid()
|
||||
r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id)
|
||||
return r
|
||||
|
||||
def model(self, model):
|
||||
""" Get an RPC proxy for the object ``model``, bound to this session.
|
||||
|
||||
:param model: an OpenERP model name
|
||||
:type model: str
|
||||
:rtype: a model object
|
||||
"""
|
||||
if self._db == False:
|
||||
raise SessionExpiredException("Session expired")
|
||||
|
||||
return Model(self, model)
|
||||
|
||||
def get_context(self):
|
||||
""" Re-initializes the current user's session context (based on
|
||||
his preferences) by calling res.users.get_context() with the old
|
||||
context
|
||||
|
||||
:returns: the new context
|
||||
"""
|
||||
assert self._uid, "The user needs to be logged-in to initialize his context"
|
||||
self.context = self.model('res.users').context_get() or {}
|
||||
self.context['uid'] = self._uid
|
||||
self._fix_lang(self.context)
|
||||
return self.context
|
||||
|
||||
def _fix_lang(self, context):
|
||||
""" OpenERP provides languages which may not make sense and/or may not
|
||||
be understood by the web client's libraries.
|
||||
|
||||
Fix those here.
|
||||
|
||||
:param dict context: context to fix
|
||||
"""
|
||||
lang = context['lang']
|
||||
|
||||
# inane OpenERP locale
|
||||
if lang == 'ar_AR':
|
||||
lang = 'ar'
|
||||
|
||||
# lang to lang_REGION (datejs only handles lang_REGION, no bare langs)
|
||||
if lang in babel.core.LOCALE_ALIASES:
|
||||
lang = babel.core.LOCALE_ALIASES[lang]
|
||||
|
||||
context['lang'] = lang or 'en_US'
|
||||
|
||||
# vim:et:ts=4:sw=4:
|
Loading…
Reference in New Issue