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
1=== modified file 'addons/web/controllers/main.py'
2--- addons/web/controllers/main.py 2013-04-24 10:20:25 +0000
3+++ addons/web/controllers/main.py 2013-05-02 09:39:32 +0000
4@@ -1129,6 +1129,7 @@
5 return self._call_kw(req, model, method, args, {})
6
7 @openerpweb.jsonrequest
8+ @openerpweb.app.route(_cp_path + '/call_kw/<model>:<method>', json=True) # for debug mode
9 def call_kw(self, req, model, method, args, kwargs):
10 return self._call_kw(req, model, method, args, kwargs)
11
12@@ -1455,8 +1456,8 @@
13 """
14 return sorted([
15 controller.fmt
16- for path, controller in openerpweb.controllers_path.iteritems()
17- if path.startswith(self._cp_path)
18+ for controller in self.__class__.__subclasses__()
19+ if controller._cp_path.startswith(self._cp_path)
20 if hasattr(controller, 'fmt')
21 ], key=operator.itemgetter("label"))
22
23
24=== modified file 'addons/web/http.py'
25--- addons/web/http.py 2013-04-24 10:20:25 +0000
26+++ addons/web/http.py 2013-05-02 09:39:32 +0000
27@@ -36,6 +36,12 @@
28
29 _logger = logging.getLogger(__name__)
30
31+
32+def str_method(method):
33+ if hasattr(method, 'im_class'):
34+ return '{0.im_class.__name__}.{0.__name__}'.format(method)
35+ return '{0.__module__}.{0.__name__}'.format(method)
36+
37 #----------------------------------------------------------
38 # RequestHandler
39 #----------------------------------------------------------
40@@ -198,7 +204,7 @@
41 self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
42 self.init(self.jsonrequest.get("params", {}))
43 if _logger.isEnabledFor(logging.DEBUG):
44- _logger.debug("--> %s.%s\n%s", method.im_class.__name__, method.__name__, pprint.pformat(self.jsonrequest))
45+ _logger.debug("--> %s\n%s", str_method(method), pprint.pformat(self.jsonrequest))
46 response['id'] = self.jsonrequest.get('id')
47 response["result"] = method(self, **self.params)
48 except session.AuthenticationError, e:
49@@ -292,7 +298,7 @@
50 akw[key] = value
51 else:
52 akw[key] = type(value)
53- _logger.debug("%s --> %s.%s %r", self.httprequest.method, method.im_class.__name__, method.__name__, akw)
54+ _logger.debug("%s --> %s %r", self.httprequest.method, str_method(method), akw)
55 try:
56 r = method(self, **self.params)
57 except Exception, e:
58@@ -352,22 +358,40 @@
59 #----------------------------------------------------------
60 # Controller registration with a metaclass
61 #----------------------------------------------------------
62-addons_module = {}
63 addons_manifest = {}
64-controllers_class = []
65-controllers_class_path = {}
66-controllers_object = {}
67-controllers_object_path = {}
68-controllers_path = {}
69+_loaded_modules = []
70
71 class ControllerType(type):
72 def __init__(cls, name, bases, attrs):
73 super(ControllerType, cls).__init__(name, bases, attrs)
74- name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls)
75- controllers_class.append(name_class)
76- path = attrs.get('_cp_path')
77- if path not in controllers_class_path:
78- controllers_class_path[path] = name_class
79+
80+ p = attrs.get('_cp_path', None)
81+ if not p:
82+ return
83+
84+ p = p.rstrip('/') + '/'
85+
86+ for k, v in attrs.items():
87+
88+ if hasattr(v, 'endpoint'):
89+ # function decorated by @route
90+ # convert func to method
91+ func, json = app._endpoint_funcs[v.endpoint]
92+ assert func == v
93+ app._endpoint_funcs[v.endpoint] = ((cls, k), json)
94+
95+ e = getattr(v, 'exposed', None)
96+ if e not in ['http', 'json']:
97+ continue
98+
99+ endpoint = '{0.__module__}.{0.__name__}.{1}'.format(cls, k)
100+
101+ if endpoint in app._endpoint_funcs and app._endpoint_funcs[endpoint][0] != (cls, k):
102+ raise RuntimeError('endpoint %r already defined as %r' % (endpoint, app._endpoint_funcs[endpoint]))
103+
104+ path = p + (k if k != 'index' else '')
105+ app._url_map.add(werkzeug.routing.Rule(path, endpoint=endpoint, host="<__domain__>"))
106+ app._endpoint_funcs[endpoint] = ((cls, k), e == 'json')
107
108 class Controller(object):
109 __metaclass__ = ControllerType
110@@ -502,21 +526,41 @@
111 raise
112 return path
113
114-class Root(object):
115- """Root WSGI application for the OpenERP Web Client.
116+class App(object):
117+ """Main WSGI application for the OpenERP Web Client.
118 """
119 def __init__(self):
120 self.addons = {}
121- self.statics = {}
122-
123- self.load_addons()
124-
125- # Setup http sessions
126+ self._url_map = werkzeug.routing.Map(host_matching=True)
127+ self._endpoint_funcs = {}
128+
129 path = session_path()
130 self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path)
131 self.session_lock = threading.Lock()
132 _logger.debug('HTTP sessions stored in: %s', path)
133
134+ def setup(self):
135+ static_dirs = self.load_addons()
136+ app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, static_dirs)
137+ self.dispatch = DisableCacheMiddleware(app)
138+
139+ def route(self, path_pattern, endpoint=None, defaults=None, host=None, json=False):
140+ """register a route for current app"""
141+ def decorator(func):
142+ _endpoint = endpoint
143+ if _endpoint is None:
144+ _endpoint = '{0.__module__}.{0.__name__}'.format(func)
145+
146+ if _endpoint in self._endpoint_funcs and self._endpoint_funcs[_endpoint][0] != func:
147+ raise RuntimeError('endpoint %r already defined as %r' % (_endpoint, self._endpoint_funcs[_endpoint]))
148+
149+ _host = host if host else "<__domain__>"
150+ self._url_map.add(werkzeug.routing.Rule(path_pattern, endpoint=_endpoint, defaults=defaults, host=_host))
151+ self._endpoint_funcs[_endpoint] = (func, json)
152+ func.endpoint = _endpoint
153+ return func
154+ return decorator
155+
156 def __call__(self, environ, start_response):
157 """ Handle a WSGI request
158 """
159@@ -533,28 +577,35 @@
160 request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
161 request.app = self
162
163- handler = self.find_handler(*(request.path.split('/')[1:]))
164-
165- if not handler:
166- response = werkzeug.exceptions.NotFound()
167- else:
168- sid = request.cookies.get('sid')
169- if not sid:
170- sid = request.args.get('sid')
171-
172- session_gc(self.session_store)
173-
174- with session_context(request, self.session_store, self.session_lock, sid) as session:
175- result = handler(request)
176-
177- if isinstance(result, basestring):
178- headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
179- response = werkzeug.wrappers.Response(result, headers=headers)
180- else:
181- response = result
182-
183- if hasattr(response, 'set_cookie'):
184- response.set_cookie('sid', session.sid)
185+ urls = self._url_map.bind_to_environ(environ)
186+
187+ def dispatcher(endpoint, values):
188+ func, isjson = self._endpoint_funcs[endpoint]
189+ builder = [HttpRequest, JsonRequest][bool(isjson)]
190+ if isinstance(func, tuple):
191+ cls, mth = func
192+ func = getattr(cls(), mth)
193+ req = builder(request)
194+ req.route_values = values
195+ return req.dispatch(func)
196+
197+ sid = request.cookies.get('sid')
198+ if not sid:
199+ sid = request.args.get('sid')
200+
201+ session_gc(self.session_store)
202+
203+ with session_context(request, self.session_store, self.session_lock, sid) as session:
204+ result = urls.dispatch(dispatcher, catch_http_exceptions=True)
205+
206+ if isinstance(result, basestring):
207+ headers = [('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
208+ response = werkzeug.wrappers.Response(result, headers=headers)
209+ else:
210+ response = result
211+
212+ if hasattr(response, 'set_cookie'):
213+ response.set_cookie('sid', session.sid)
214
215 return response(environ, start_response)
216
217@@ -564,62 +615,26 @@
218
219 for addons_path in openerp.modules.module.ad_paths:
220 for module in sorted(os.listdir(addons_path)):
221- if module not in addons_module:
222+ fqn = 'openerp.addons.' + module
223+ if fqn not in _loaded_modules:
224 manifest_path = os.path.join(addons_path, module, '__openerp__.py')
225 path_static = os.path.join(addons_path, module, 'static')
226 if os.path.isfile(manifest_path) and os.path.isdir(path_static):
227 manifest = ast.literal_eval(open(manifest_path).read())
228 manifest['addons_path'] = addons_path
229- _logger.debug("Loading %s", module)
230- if 'openerp.addons' in sys.modules:
231- m = __import__('openerp.addons.' + module)
232- else:
233- m = __import__(module)
234- addons_module[module] = m
235+ if fqn not in sys.modules:
236+ __import__(fqn)
237 addons_manifest[module] = manifest
238- self.statics['/%s/static' % module] = path_static
239-
240- for k, v in controllers_class_path.items():
241- if k not in controllers_object_path and hasattr(v[1], '_cp_path'):
242- o = v[1]()
243- controllers_object[v[0]] = o
244+ _loaded_modules.append(fqn)
245+ statics['/%s/static' % module] = path_static
246 controllers_object_path[k] = o
247- if hasattr(o, '_cp_path'):
248- controllers_path[o._cp_path] = o
249-
250- app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics)
251- self.dispatch = DisableCacheMiddleware(app)
252-
253- def find_handler(self, *l):
254- """
255- Tries to discover the controller handling the request for the path
256- specified by the provided parameters
257-
258- :param l: path sections to a controller or controller method
259- :returns: a callable matching the path sections, or ``None``
260- :rtype: ``Controller | None``
261- """
262- if l:
263- ps = '/' + '/'.join(filter(None, l))
264- method_name = 'index'
265- while ps:
266- c = controllers_path.get(ps)
267- if c:
268- method = getattr(c, method_name, None)
269- if method:
270- exposed = getattr(method, 'exposed', False)
271- if exposed == 'json':
272- _logger.debug("Dispatch json to %s %s %s", ps, c, method_name)
273- return lambda request: JsonRequest(request).dispatch(method)
274- elif exposed == 'http':
275- _logger.debug("Dispatch http to %s %s %s", ps, c, method_name)
276- return lambda request: HttpRequest(request).dispatch(method)
277- ps, _slash, method_name = ps.rpartition('/')
278- if not ps and method_name:
279- ps = '/'
280- return None
281+
282+ return statics
283+
284+app = App()
285
286 def wsgi_postload():
287- openerp.wsgi.register_wsgi_handler(Root())
288+ app.setup()
289+ openerp.wsgi.register_wsgi_handler(app)
290
291 # vim:et:ts=4:sw=4: