Merge lp:~openerp-dev/openerp-web/trunk-routedeco-chs into lp:openerp-web

Proposed by Antony Lesuisse (OpenERP)
Status: Work in progress
Proposed branch: lp:~openerp-dev/openerp-web/trunk-routedeco-chs
Merge into: lp:openerp-web
Diff against target: 291 lines (+108/-92)
2 files modified
addons/web/controllers/main.py (+3/-2)
addons/web/http.py (+105/-90)
To merge this branch: bzr merge lp:~openerp-dev/openerp-web/trunk-routedeco-chs
Reviewer Review Type Date Requested Status
OpenERP R&D Web Team Pending
Review via email: mp+136019@code.launchpad.net
To post a comment you must log in.
3493. By Christophe Simonis (OpenERP)

merge upstream

3494. By Christophe Simonis (OpenERP)

merge upstream

Unmerged revisions

3494. By Christophe Simonis (OpenERP)

merge upstream

3493. By Christophe Simonis (OpenERP)

merge upstream

3492. By Christophe Simonis (OpenERP)

[FIX] do not reimport already imported modules

3491. By Christophe Simonis (OpenERP)

[FIX] correct logging of plain function

3490. By Christophe Simonis (OpenERP)

[FIX] allow multiple routes for a function

3489. By Christophe Simonis (OpenERP)

[FIX] host_matching

3488. By Christophe Simonis (OpenERP)

[FIX] deactivate host_matching for routes

3487. By Christophe Simonis (OpenERP)

[IMP] web: use werkzeug for routing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'addons/web/controllers/main.py'
--- addons/web/controllers/main.py 2013-04-24 10:20:25 +0000
+++ addons/web/controllers/main.py 2013-05-02 09:39:32 +0000
@@ -1129,6 +1129,7 @@
1129 return self._call_kw(req, model, method, args, {})1129 return self._call_kw(req, model, method, args, {})
11301130
1131 @openerpweb.jsonrequest1131 @openerpweb.jsonrequest
1132 @openerpweb.app.route(_cp_path + '/call_kw/<model>:<method>', json=True) # for debug mode
1132 def call_kw(self, req, model, method, args, kwargs):1133 def call_kw(self, req, model, method, args, kwargs):
1133 return self._call_kw(req, model, method, args, kwargs)1134 return self._call_kw(req, model, method, args, kwargs)
11341135
@@ -1455,8 +1456,8 @@
1455 """1456 """
1456 return sorted([1457 return sorted([
1457 controller.fmt1458 controller.fmt
1458 for path, controller in openerpweb.controllers_path.iteritems()1459 for controller in self.__class__.__subclasses__()
1459 if path.startswith(self._cp_path)1460 if controller._cp_path.startswith(self._cp_path)
1460 if hasattr(controller, 'fmt')1461 if hasattr(controller, 'fmt')
1461 ], key=operator.itemgetter("label"))1462 ], key=operator.itemgetter("label"))
14621463
14631464
=== modified file 'addons/web/http.py'
--- addons/web/http.py 2013-04-24 10:20:25 +0000
+++ addons/web/http.py 2013-05-02 09:39:32 +0000
@@ -36,6 +36,12 @@
3636
37_logger = logging.getLogger(__name__)37_logger = logging.getLogger(__name__)
3838
39
40def str_method(method):
41 if hasattr(method, 'im_class'):
42 return '{0.im_class.__name__}.{0.__name__}'.format(method)
43 return '{0.__module__}.{0.__name__}'.format(method)
44
39#----------------------------------------------------------45#----------------------------------------------------------
40# RequestHandler46# RequestHandler
41#----------------------------------------------------------47#----------------------------------------------------------
@@ -198,7 +204,7 @@
198 self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)204 self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
199 self.init(self.jsonrequest.get("params", {}))205 self.init(self.jsonrequest.get("params", {}))
200 if _logger.isEnabledFor(logging.DEBUG):206 if _logger.isEnabledFor(logging.DEBUG):
201 _logger.debug("--> %s.%s\n%s", method.im_class.__name__, method.__name__, pprint.pformat(self.jsonrequest))207 _logger.debug("--> %s\n%s", str_method(method), pprint.pformat(self.jsonrequest))
202 response['id'] = self.jsonrequest.get('id')208 response['id'] = self.jsonrequest.get('id')
203 response["result"] = method(self, **self.params)209 response["result"] = method(self, **self.params)
204 except session.AuthenticationError, e:210 except session.AuthenticationError, e:
@@ -292,7 +298,7 @@
292 akw[key] = value298 akw[key] = value
293 else:299 else:
294 akw[key] = type(value)300 akw[key] = type(value)
295 _logger.debug("%s --> %s.%s %r", self.httprequest.method, method.im_class.__name__, method.__name__, akw)301 _logger.debug("%s --> %s %r", self.httprequest.method, str_method(method), akw)
296 try:302 try:
297 r = method(self, **self.params)303 r = method(self, **self.params)
298 except Exception, e:304 except Exception, e:
@@ -352,22 +358,40 @@
352#----------------------------------------------------------358#----------------------------------------------------------
353# Controller registration with a metaclass359# Controller registration with a metaclass
354#----------------------------------------------------------360#----------------------------------------------------------
355addons_module = {}
356addons_manifest = {}361addons_manifest = {}
357controllers_class = []362_loaded_modules = []
358controllers_class_path = {}
359controllers_object = {}
360controllers_object_path = {}
361controllers_path = {}
362363
363class ControllerType(type):364class ControllerType(type):
364 def __init__(cls, name, bases, attrs):365 def __init__(cls, name, bases, attrs):
365 super(ControllerType, cls).__init__(name, bases, attrs)366 super(ControllerType, cls).__init__(name, bases, attrs)
366 name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls)367
367 controllers_class.append(name_class)368 p = attrs.get('_cp_path', None)
368 path = attrs.get('_cp_path')369 if not p:
369 if path not in controllers_class_path:370 return
370 controllers_class_path[path] = name_class371
372 p = p.rstrip('/') + '/'
373
374 for k, v in attrs.items():
375
376 if hasattr(v, 'endpoint'):
377 # function decorated by @route
378 # convert func to method
379 func, json = app._endpoint_funcs[v.endpoint]
380 assert func == v
381 app._endpoint_funcs[v.endpoint] = ((cls, k), json)
382
383 e = getattr(v, 'exposed', None)
384 if e not in ['http', 'json']:
385 continue
386
387 endpoint = '{0.__module__}.{0.__name__}.{1}'.format(cls, k)
388
389 if endpoint in app._endpoint_funcs and app._endpoint_funcs[endpoint][0] != (cls, k):
390 raise RuntimeError('endpoint %r already defined as %r' % (endpoint, app._endpoint_funcs[endpoint]))
391
392 path = p + (k if k != 'index' else '')
393 app._url_map.add(werkzeug.routing.Rule(path, endpoint=endpoint, host="<__domain__>"))
394 app._endpoint_funcs[endpoint] = ((cls, k), e == 'json')
371395
372class Controller(object):396class Controller(object):
373 __metaclass__ = ControllerType397 __metaclass__ = ControllerType
@@ -502,21 +526,41 @@
502 raise526 raise
503 return path527 return path
504528
505class Root(object):529class App(object):
506 """Root WSGI application for the OpenERP Web Client.530 """Main WSGI application for the OpenERP Web Client.
507 """531 """
508 def __init__(self):532 def __init__(self):
509 self.addons = {}533 self.addons = {}
510 self.statics = {}534 self._url_map = werkzeug.routing.Map(host_matching=True)
511535 self._endpoint_funcs = {}
512 self.load_addons()536
513
514 # Setup http sessions
515 path = session_path()537 path = session_path()
516 self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path)538 self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path)
517 self.session_lock = threading.Lock()539 self.session_lock = threading.Lock()
518 _logger.debug('HTTP sessions stored in: %s', path)540 _logger.debug('HTTP sessions stored in: %s', path)
519541
542 def setup(self):
543 static_dirs = self.load_addons()
544 app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, static_dirs)
545 self.dispatch = DisableCacheMiddleware(app)
546
547 def route(self, path_pattern, endpoint=None, defaults=None, host=None, json=False):
548 """register a route for current app"""
549 def decorator(func):
550 _endpoint = endpoint
551 if _endpoint is None:
552 _endpoint = '{0.__module__}.{0.__name__}'.format(func)
553
554 if _endpoint in self._endpoint_funcs and self._endpoint_funcs[_endpoint][0] != func:
555 raise RuntimeError('endpoint %r already defined as %r' % (_endpoint, self._endpoint_funcs[_endpoint]))
556
557 _host = host if host else "<__domain__>"
558 self._url_map.add(werkzeug.routing.Rule(path_pattern, endpoint=_endpoint, defaults=defaults, host=_host))
559 self._endpoint_funcs[_endpoint] = (func, json)
560 func.endpoint = _endpoint
561 return func
562 return decorator
563
520 def __call__(self, environ, start_response):564 def __call__(self, environ, start_response):
521 """ Handle a WSGI request565 """ Handle a WSGI request
522 """566 """
@@ -533,28 +577,35 @@
533 request.parameter_storage_class = werkzeug.datastructures.ImmutableDict577 request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
534 request.app = self578 request.app = self
535579
536 handler = self.find_handler(*(request.path.split('/')[1:]))580 urls = self._url_map.bind_to_environ(environ)
537581
538 if not handler:582 def dispatcher(endpoint, values):
539 response = werkzeug.exceptions.NotFound()583 func, isjson = self._endpoint_funcs[endpoint]
540 else:584 builder = [HttpRequest, JsonRequest][bool(isjson)]
541 sid = request.cookies.get('sid')585 if isinstance(func, tuple):
542 if not sid:586 cls, mth = func
543 sid = request.args.get('sid')587 func = getattr(cls(), mth)
544588 req = builder(request)
545 session_gc(self.session_store)589 req.route_values = values
546590 return req.dispatch(func)
547 with session_context(request, self.session_store, self.session_lock, sid) as session:591
548 result = handler(request)592 sid = request.cookies.get('sid')
549593 if not sid:
550 if isinstance(result, basestring):594 sid = request.args.get('sid')
551 headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]595
552 response = werkzeug.wrappers.Response(result, headers=headers)596 session_gc(self.session_store)
553 else:597
554 response = result598 with session_context(request, self.session_store, self.session_lock, sid) as session:
555599 result = urls.dispatch(dispatcher, catch_http_exceptions=True)
556 if hasattr(response, 'set_cookie'):600
557 response.set_cookie('sid', session.sid)601 if isinstance(result, basestring):
602 headers = [('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
603 response = werkzeug.wrappers.Response(result, headers=headers)
604 else:
605 response = result
606
607 if hasattr(response, 'set_cookie'):
608 response.set_cookie('sid', session.sid)
558609
559 return response(environ, start_response)610 return response(environ, start_response)
560611
@@ -564,62 +615,26 @@
564615
565 for addons_path in openerp.modules.module.ad_paths:616 for addons_path in openerp.modules.module.ad_paths:
566 for module in sorted(os.listdir(addons_path)):617 for module in sorted(os.listdir(addons_path)):
567 if module not in addons_module:618 fqn = 'openerp.addons.' + module
619 if fqn not in _loaded_modules:
568 manifest_path = os.path.join(addons_path, module, '__openerp__.py')620 manifest_path = os.path.join(addons_path, module, '__openerp__.py')
569 path_static = os.path.join(addons_path, module, 'static')621 path_static = os.path.join(addons_path, module, 'static')
570 if os.path.isfile(manifest_path) and os.path.isdir(path_static):622 if os.path.isfile(manifest_path) and os.path.isdir(path_static):
571 manifest = ast.literal_eval(open(manifest_path).read())623 manifest = ast.literal_eval(open(manifest_path).read())
572 manifest['addons_path'] = addons_path624 manifest['addons_path'] = addons_path
573 _logger.debug("Loading %s", module)625 if fqn not in sys.modules:
574 if 'openerp.addons' in sys.modules:626 __import__(fqn)
575 m = __import__('openerp.addons.' + module)
576 else:
577 m = __import__(module)
578 addons_module[module] = m
579 addons_manifest[module] = manifest627 addons_manifest[module] = manifest
580 self.statics['/%s/static' % module] = path_static628 _loaded_modules.append(fqn)
581629 statics['/%s/static' % module] = path_static
582 for k, v in controllers_class_path.items():
583 if k not in controllers_object_path and hasattr(v[1], '_cp_path'):
584 o = v[1]()
585 controllers_object[v[0]] = o
586 controllers_object_path[k] = o630 controllers_object_path[k] = o
587 if hasattr(o, '_cp_path'):631
588 controllers_path[o._cp_path] = o632 return statics
589633
590 app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics)634app = App()
591 self.dispatch = DisableCacheMiddleware(app)
592
593 def find_handler(self, *l):
594 """
595 Tries to discover the controller handling the request for the path
596 specified by the provided parameters
597
598 :param l: path sections to a controller or controller method
599 :returns: a callable matching the path sections, or ``None``
600 :rtype: ``Controller | None``
601 """
602 if l:
603 ps = '/' + '/'.join(filter(None, l))
604 method_name = 'index'
605 while ps:
606 c = controllers_path.get(ps)
607 if c:
608 method = getattr(c, method_name, None)
609 if method:
610 exposed = getattr(method, 'exposed', False)
611 if exposed == 'json':
612 _logger.debug("Dispatch json to %s %s %s", ps, c, method_name)
613 return lambda request: JsonRequest(request).dispatch(method)
614 elif exposed == 'http':
615 _logger.debug("Dispatch http to %s %s %s", ps, c, method_name)
616 return lambda request: HttpRequest(request).dispatch(method)
617 ps, _slash, method_name = ps.rpartition('/')
618 if not ps and method_name:
619 ps = '/'
620 return None
621635
622def wsgi_postload():636def wsgi_postload():
623 openerp.wsgi.register_wsgi_handler(Root())637 app.setup()
638 openerp.wsgi.register_wsgi_handler(app)
624639
625# vim:et:ts=4:sw=4:640# vim:et:ts=4:sw=4: