Merge lp:~openerp-dev/openerp-web/trunk-bug-1220203-jar into lp:openerp-web

Proposed by Jaydeep Barot(OpenERP)
Status: Rejected
Rejected by: Thibault Delavallée (OpenERP)
Proposed branch: lp:~openerp-dev/openerp-web/trunk-bug-1220203-jar
Merge into: lp:openerp-web
Diff against target: 1122 lines (+1118/-0)
1 file modified
addons/web/http.py.THIS (+1118/-0)
To merge this branch: bzr merge lp:~openerp-dev/openerp-web/trunk-bug-1220203-jar
Reviewer Review Type Date Requested Status
Thibault Delavallée (OpenERP) (community) Needs Fixing
Atul Patel(OpenERP) (community) Approve
Review via email: mp+184968@code.launchpad.net

Description of the change

Hello,

[trunk] Fix upload attachment file in chatter view.

Thanks,
Jaydeep Barot.

To post a comment you must log in.
Revision history for this message
Atul Patel(OpenERP) (atp-openerp) wrote :

Hello,

it seems ok for me.

THanks

review: Approve
3835. By Mehul Mehta(OpenERP)

[Merge] with main main web

Revision history for this message
Thibault Delavallée (OpenERP) (tde-openerp) wrote :

Hello,

Could you re-sync your branch with web, now that http moved to server ?

review: Needs Fixing
Revision history for this message
Thibault Delavallée (OpenERP) (tde-openerp) wrote :

The related bug is already fixed. I therefore reject this branch, as there is no need to work furthuer on it.

Unmerged revisions

3835. By Mehul Mehta(OpenERP)

[Merge] with main main web

3834. By Jaydeep Barot(OpenERP)

[FIX] fix upload attachment in chatter

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'addons/web/http.py.THIS'
--- addons/web/http.py.THIS 1970-01-01 00:00:00 +0000
+++ addons/web/http.py.THIS 2013-10-29 08:41:10 +0000
@@ -0,0 +1,1118 @@
1# -*- coding: utf-8 -*-
2#----------------------------------------------------------
3# OpenERP Web HTTP layer
4#----------------------------------------------------------
5import ast
6import cgi
7import contextlib
8import functools
9import getpass
10import logging
11import mimetypes
12import os
13import pprint
14import random
15import sys
16import tempfile
17import threading
18import time
19import traceback
20import urlparse
21import uuid
22import errno
23import re
24
25import babel.core
26import simplejson
27import werkzeug.contrib.sessions
28import werkzeug.datastructures
29import werkzeug.exceptions
30import werkzeug.utils
31import werkzeug.wrappers
32import werkzeug.wsgi
33import werkzeug.routing as routing
34import urllib
35import urllib2
36
37import openerp
38import openerp.service.security as security
39from openerp.tools import config
40
41import inspect
42import functools
43
44_logger = logging.getLogger(__name__)
45
46#----------------------------------------------------------
47# RequestHandler
48#----------------------------------------------------------
49class WebRequest(object):
50 """ Parent class for all OpenERP Web request types, mostly deals with
51 initialization and setup of the request object (the dispatching itself has
52 to be handled by the subclasses)
53
54 :param request: a wrapped werkzeug Request object
55 :type request: :class:`werkzeug.wrappers.BaseRequest`
56
57 .. attribute:: httprequest
58
59 the original :class:`werkzeug.wrappers.Request` object provided to the
60 request
61
62 .. attribute:: httpsession
63
64 .. deprecated:: 8.0
65
66 Use ``self.session`` instead.
67
68 .. attribute:: params
69
70 :class:`~collections.Mapping` of request parameters, not generally
71 useful as they're provided directly to the handler method as keyword
72 arguments
73
74 .. attribute:: session_id
75
76 opaque identifier for the :class:`session.OpenERPSession` instance of
77 the current request
78
79 .. attribute:: session
80
81 a :class:`OpenERPSession` holding the HTTP session data for the
82 current http session
83
84 .. attribute:: context
85
86 :class:`~collections.Mapping` of context values for the current request
87
88 .. attribute:: db
89
90 ``str``, the name of the database linked to the current request. Can be ``None``
91 if the current request uses the ``none`` authentication.
92
93 .. attribute:: uid
94
95 ``int``, the id of the user related to the current request. Can be ``None``
96 if the current request uses the ``none`` authenticatoin.
97 """
98 def __init__(self, httprequest):
99 self.httprequest = httprequest
100 self.httpresponse = None
101 self.httpsession = httprequest.session
102 self.session = httprequest.session
103 self.session_id = httprequest.session.sid
104 self.disable_db = False
105 self.uid = None
106 self.func = None
107 self.auth_method = None
108 self._cr_cm = None
109 self._cr = None
110 self.func_request_type = None
111 # set db/uid trackers - they're cleaned up at the WSGI
112 # dispatching phase in openerp.service.wsgi_server.application
113 if self.db:
114 threading.current_thread().dbname = self.db
115 if self.session.uid:
116 threading.current_thread().uid = self.session.uid
117 self.context = dict(self.session.context)
118 self.lang = self.context["lang"]
119
120 def _authenticate(self):
121 if self.session.uid:
122 try:
123 self.session.check_security()
124 except SessionExpiredException, e:
125 self.session.logout()
126 raise SessionExpiredException("Session expired for request %s" % self.httprequest)
127 auth_methods[self.auth_method]()
128 @property
129 def registry(self):
130 """
131 The registry to the database linked to this request. Can be ``None`` if the current request uses the
132 ``none'' authentication.
133 """
134 return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
135
136 @property
137 def db(self):
138 """
139 The registry to the database linked to this request. Can be ``None`` if the current request uses the
140 ``none'' authentication.
141 """
142 return self.session.db if not self.disable_db else None
143
144 @property
145 def cr(self):
146 """
147 The cursor initialized for the current method call. If the current request uses the ``none`` authentication
148 trying to access this property will raise an exception.
149 """
150 # some magic to lazy create the cr
151 if not self._cr_cm:
152 self._cr_cm = self.registry.cursor()
153 self._cr = self._cr_cm.__enter__()
154 return self._cr
155
156 def _call_function(self, *args, **kwargs):
157 self._authenticate()
158 try:
159 # ugly syntax only to get the __exit__ arguments to pass to self._cr
160 request = self
161 class with_obj(object):
162 def __enter__(self):
163 pass
164 def __exit__(self, *args):
165 if request._cr_cm:
166 request._cr_cm.__exit__(*args)
167 request._cr_cm = None
168 request._cr = None
169
170 with with_obj():
171 if self.func_request_type != self._request_type:
172 raise Exception("%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \
173 % (self.func, self.httprequest.path, self.func_request_type, self._request_type))
174 return self.func(*args, **kwargs)
175 finally:
176 # just to be sure no one tries to re-use the request
177 self.disable_db = True
178 self.uid = None
179
180def auth_method_user():
181 request.uid = request.session.uid
182
183def auth_method_admin():
184 if not request.db:
185 raise SessionExpiredException("No valid database for request %s" % request.httprequest)
186 request.uid = openerp.SUPERUSER_ID
187
188def auth_method_none():
189 request.disable_db = True
190 request.uid = None
191
192auth_methods = {
193 "user": auth_method_user,
194 "admin": auth_method_admin,
195 "none": auth_method_none,
196}
197
198def route(route, type="http", auth="user"):
199 """
200 Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
201 of ``Controller``.
202
203 :param route: string or array. The route part that will determine which http requests will match the decorated
204 method. Can be a single string or an array of strings. See werkzeug's routing documentation for the format of
205 route expression ( http://werkzeug.pocoo.org/docs/routing/ ).
206 :param type: The type of request, can be ``'http'`` or ``'json'``.
207 :param auth: The type of authentication method, can on of the following:
208
209 * ``user``: The user must be authenticated and the current request will perform using the rights of the
210 user.
211 * ``admin``: The user may not be authenticated and the current request will perform using the admin user.
212 * ``none``: The method is always active, even if there is no database. Mainly used by the framework and
213 authentication modules. There request code will not have any facilities to access the database nor have any
214 configuration indicating the current database nor the current user.
215 """
216 assert type in ["http", "json"]
217 assert auth in auth_methods.keys()
218 def decorator(f):
219 if isinstance(route, list):
220 f.routes = route
221 else:
222 f.routes = [route]
223 f.exposed = type
224 if getattr(f, "auth", None) is None:
225 f.auth = auth
226 return f
227 return decorator
228
229def reject_nonliteral(dct):
230 if '__ref' in dct:
231 raise ValueError(
232 "Non literal contexts can not be sent to the server anymore (%r)" % (dct,))
233 return dct
234
235class JsonRequest(WebRequest):
236 """ JSON-RPC2 over HTTP.
237
238 Sucessful request::
239
240 --> {"jsonrpc": "2.0",
241 "method": "call",
242 "params": {"context": {},
243 "arg1": "val1" },
244 "id": null}
245
246 <-- {"jsonrpc": "2.0",
247 "result": { "res1": "val1" },
248 "id": null}
249
250 Request producing a error::
251
252 --> {"jsonrpc": "2.0",
253 "method": "call",
254 "params": {"context": {},
255 "arg1": "val1" },
256 "id": null}
257
258 <-- {"jsonrpc": "2.0",
259 "error": {"code": 1,
260 "message": "End user error message.",
261 "data": {"code": "codestring",
262 "debug": "traceback" } },
263 "id": null}
264
265 """
266 _request_type = "json"
267
268 def __init__(self, *args):
269 super(JsonRequest, self).__init__(*args)
270
271 self.jsonp_handler = None
272
273 args = self.httprequest.args
274 jsonp = args.get('jsonp')
275 self.jsonp = jsonp
276 request = None
277 request_id = args.get('id')
278
279 if jsonp and self.httprequest.method == 'POST':
280 # jsonp 2 steps step1 POST: save call
281 def handler():
282 self.session.jsonp_requests[request_id] = self.httprequest.form['r']
283 self.session.modified = True
284 headers=[('Content-Type', 'text/plain; charset=utf-8')]
285 r = werkzeug.wrappers.Response(request_id, headers=headers)
286 return r
287 self.jsonp_handler = handler
288 return
289 elif jsonp and args.get('r'):
290 # jsonp method GET
291 request = args.get('r')
292 elif jsonp and request_id:
293 # jsonp 2 steps step2 GET: run and return result
294 request = self.session.jsonp_requests.pop(request_id, "")
295 else:
296 # regular jsonrpc2
297 request = self.httprequest.stream.read()
298
299 # Read POST content or POST Form Data named "request"
300 self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
301 self.params = dict(self.jsonrequest.get("params", {}))
302 self.context = self.params.pop('context', self.session.context)
303
304 def dispatch(self):
305 """ Calls the method asked for by the JSON-RPC2 or JSONP request
306 """
307 if self.jsonp_handler:
308 return self.jsonp_handler()
309 response = {"jsonrpc": "2.0" }
310 error = None
311
312 try:
313 response['id'] = self.jsonrequest.get('id')
314 response["result"] = self._call_function(**self.params)
315 except AuthenticationError, e:
316 _logger.exception("Exception during JSON request handling.")
317 se = serialize_exception(e)
318 error = {
319 'code': 100,
320 'message': "OpenERP Session Invalid",
321 'data': se
322 }
323 except Exception, e:
324 _logger.exception("Exception during JSON request handling.")
325 se = serialize_exception(e)
326 error = {
327 'code': 200,
328 'message': "OpenERP Server Error",
329 'data': se
330 }
331 if error:
332 response["error"] = error
333
334 if self.jsonp:
335 # If we use jsonp, that's mean we are called from another host
336 # Some browser (IE and Safari) do no allow third party cookies
337 # We need then to manage http sessions manually.
338 response['session_id'] = self.session_id
339 mime = 'application/javascript'
340 body = "%s(%s);" % (self.jsonp, simplejson.dumps(response),)
341 else:
342 mime = 'application/json'
343 body = simplejson.dumps(response)
344
345 r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
346 return r
347
348def serialize_exception(e):
349 tmp = {
350 "name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__,
351 "debug": traceback.format_exc(),
352 "message": u"%s" % e,
353 "arguments": to_jsonable(e.args),
354 }
355 if isinstance(e, openerp.osv.osv.except_osv):
356 tmp["exception_type"] = "except_osv"
357 elif isinstance(e, openerp.exceptions.Warning):
358 tmp["exception_type"] = "warning"
359 elif isinstance(e, openerp.exceptions.AccessError):
360 tmp["exception_type"] = "access_error"
361 elif isinstance(e, openerp.exceptions.AccessDenied):
362 tmp["exception_type"] = "access_denied"
363 return tmp
364
365def to_jsonable(o):
366 if isinstance(o, str) or isinstance(o,unicode) or isinstance(o, int) or isinstance(o, long) \
367 or isinstance(o, bool) or o is None or isinstance(o, float):
368 return o
369 if isinstance(o, list) or isinstance(o, tuple):
370 return [to_jsonable(x) for x in o]
371 if isinstance(o, dict):
372 tmp = {}
373 for k, v in o.items():
374 tmp[u"%s" % k] = to_jsonable(v)
375 return tmp
376 return u"%s" % o
377
378def jsonrequest(f):
379 """
380 .. deprecated:: 8.0
381
382 Use the ``route()`` decorator instead.
383 """
384 f.combine = True
385 base = f.__name__
386 if f.__name__ == "index":
387 base = ""
388 return route([base, os.path.join(base, "<path:_ignored_path>")], type="json", auth="user")(f)
389
390class HttpRequest(WebRequest):
391 """ Regular GET/POST request
392 """
393 _request_type = "http"
394
395 def __init__(self, *args):
396 super(HttpRequest, self).__init__(*args)
397 params = dict(self.httprequest.args)
398 params.update(self.httprequest.form)
399 params.update(self.httprequest.files)
400 ex = set(["session_id"])
401 for k in params.keys():
402 if k in ex:
403 del params[k]
404 self.params = params
405
406 def dispatch(self):
407 akw = {}
408 for key, value in self.httprequest.args.iteritems():
409 if isinstance(value, basestring) and len(value) < 1024:
410 akw[key] = value
411 else:
412 akw[key] = type(value)
413 try:
414 r = self._call_function(**self.params)
415 except werkzeug.exceptions.HTTPException, e:
416 r = e
417 except Exception, e:
418 _logger.exception("An exception occured during an http request")
419 se = serialize_exception(e)
420 error = {
421 'code': 200,
422 'message': "OpenERP Server Error",
423 'data': se
424 }
425 r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps(error)))
426 else:
427 if not r:
428 r = werkzeug.wrappers.Response(status=204) # no content
429 return r
430
431 def make_response(self, data, headers=None, cookies=None):
432 """ Helper for non-HTML responses, or HTML responses with custom
433 response headers or cookies.
434
435 While handlers can just return the HTML markup of a page they want to
436 send as a string if non-HTML data is returned they need to create a
437 complete response object, or the returned data will not be correctly
438 interpreted by the clients.
439
440 :param basestring data: response body
441 :param headers: HTTP headers to set on the response
442 :type headers: ``[(name, value)]``
443 :param collections.Mapping cookies: cookies to set on the client
444 """
445 response = werkzeug.wrappers.Response(data, headers=headers)
446 if cookies:
447 for k, v in cookies.iteritems():
448 response.set_cookie(k, v)
449 return response
450
451 def not_found(self, description=None):
452 """ Helper for 404 response, return its result from the method
453 """
454 return werkzeug.exceptions.NotFound(description)
455
456def httprequest(f):
457 """
458 .. deprecated:: 8.0
459
460 Use the ``route()`` decorator instead.
461 """
462 f.combine = True
463 base = f.__name__
464 if f.__name__ == "index":
465 base = ""
466 return route([base, os.path.join(base, "<path:_ignored_path>")], type="http", auth="user")(f)
467
468#----------------------------------------------------------
469# Local storage of requests
470#----------------------------------------------------------
471from werkzeug.local import LocalStack
472
473_request_stack = LocalStack()
474
475def set_request(request):
476 class with_obj(object):
477 def __enter__(self):
478 _request_stack.push(request)
479 def __exit__(self, *args):
480 _request_stack.pop()
481 return with_obj()
482
483"""
484 A global proxy that always redirect to the current request object.
485"""
486request = _request_stack()
487
488#----------------------------------------------------------
489# Controller registration with a metaclass
490#----------------------------------------------------------
491addons_module = {}
492addons_manifest = {}
493controllers_per_module = {}
494
495class ControllerType(type):
496 def __init__(cls, name, bases, attrs):
497 super(ControllerType, cls).__init__(name, bases, attrs)
498
499 # create wrappers for old-style methods with req as first argument
500 cls._methods_wrapper = {}
501 for k, v in attrs.items():
502 if inspect.isfunction(v):
503 spec = inspect.getargspec(v)
504 first_arg = spec.args[1] if len(spec.args) >= 2 else None
505 if first_arg in ["req", "request"]:
506 def build_new(nv):
507 return lambda self, *args, **kwargs: nv(self, request, *args, **kwargs)
508 cls._methods_wrapper[k] = build_new(v)
509
510 # store the controller in the controllers list
511 name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls)
512 class_path = name_class[0].split(".")
513 if not class_path[:2] == ["openerp", "addons"]:
514 return
515 # we want to know all modules that have controllers
516 module = class_path[2]
517 controllers_per_module.setdefault(module, [])
518 # but we only store controllers directly inheriting from Controller
519 if not "Controller" in globals() or not Controller in bases:
520 return
521 controllers_per_module.setdefault(module, []).append(name_class)
522
523class Controller(object):
524 __metaclass__ = ControllerType
525
526 def get_wrapped_method(self, name):
527 if name in self.__class__._methods_wrapper:
528 return functools.partial(self.__class__._methods_wrapper[name], self)
529 else:
530 return getattr(self, name)
531
532#############################
533# OpenERP Sessions #
534#############################
535
536class AuthenticationError(Exception):
537 pass
538
539class SessionExpiredException(Exception):
540 pass
541
542class Service(object):
543 """
544 .. deprecated:: 8.0
545 Use ``openerp.netsvc.dispatch_rpc()`` instead.
546 """
547 def __init__(self, session, service_name):
548 self.session = session
549 self.service_name = service_name
550
551 def __getattr__(self, method):
552 def proxy_method(*args):
553 result = openerp.netsvc.dispatch_rpc(self.service_name, method, args)
554 return result
555 return proxy_method
556
557class Model(object):
558 """
559 .. deprecated:: 8.0
560 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
561 """
562 def __init__(self, session, model):
563 self.session = session
564 self.model = model
565 self.proxy = self.session.proxy('object')
566
567 def __getattr__(self, method):
568 self.session.assert_valid()
569 def proxy(*args, **kw):
570 # Can't provide any retro-compatibility for this case, so we check it and raise an Exception
571 # to tell the programmer to adapt his code
572 if not request.db or not request.uid or self.session.db != request.db \
573 or self.session.uid != request.uid:
574 raise Exception("Trying to use Model with badly configured database or user.")
575
576 mod = request.registry.get(self.model)
577 if method.startswith('_'):
578 raise Exception("Access denied")
579 meth = getattr(mod, method)
580 cr = request.cr
581 result = meth(cr, request.uid, *args, **kw)
582 # reorder read
583 if method == "read":
584 if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
585 index = {}
586 for r in result:
587 index[r['id']] = r
588 result = [index[x] for x in args[0] if x in index]
589 return result
590 return proxy
591
592class OpenERPSession(werkzeug.contrib.sessions.Session):
593 def __init__(self, *args, **kwargs):
594 self.inited = False
595 self.modified = False
596 super(OpenERPSession, self).__init__(*args, **kwargs)
597 self.inited = True
598 self._default_values()
599 self.modified = False
600
601 def __getattr__(self, attr):
602 return self.get(attr, None)
603 def __setattr__(self, k, v):
604 if getattr(self, "inited", False):
605 try:
606 object.__getattribute__(self, k)
607 except:
608 return self.__setitem__(k, v)
609 object.__setattr__(self, k, v)
610
611 def authenticate(self, db, login=None, password=None, uid=None):
612 """
613 Authenticate the current user with the given db, login and password. If successful, store
614 the authentication parameters in the current session and request.
615
616 :param uid: If not None, that user id will be used instead the login to authenticate the user.
617 """
618
619 if uid is None:
620 wsgienv = request.httprequest.environ
621 env = dict(
622 base_location=request.httprequest.url_root.rstrip('/'),
623 HTTP_HOST=wsgienv['HTTP_HOST'],
624 REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
625 )
626 uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
627 else:
628 security.check(db, uid, password)
629 self.db = db
630 self.uid = uid
631 self.login = login
632 self.password = password
633 request.uid = uid
634 request.disable_db = False
635
636 if uid: self.get_context()
637 return uid
638
639 def check_security(self):
640 """
641 Chech the current authentication parameters to know if those are still valid. This method
642 should be called at each request. If the authentication fails, a ``SessionExpiredException``
643 is raised.
644 """
645 if not self.db or not self.uid:
646 raise SessionExpiredException("Session expired")
647 security.check(self.db, self.uid, self.password)
648
649 def logout(self):
650 for k in self.keys():
651 del self[k]
652 self._default_values()
653
654 def _default_values(self):
655 self.setdefault("db", None)
656 self.setdefault("uid", None)
657 self.setdefault("login", None)
658 self.setdefault("password", None)
659 self.setdefault("context", {'tz': "UTC", "uid": None})
660 self.setdefault("jsonp_requests", {})
661
662 def get_context(self):
663 """
664 Re-initializes the current user's session context (based on
665 his preferences) by calling res.users.get_context() with the old
666 context.
667
668 :returns: the new context
669 """
670 assert self.uid, "The user needs to be logged-in to initialize his context"
671 self.context = request.registry.get('res.users').context_get(request.cr, request.uid) or {}
672 self.context['uid'] = self.uid
673 self._fix_lang(self.context)
674 return self.context
675
676 def _fix_lang(self, context):
677 """ OpenERP provides languages which may not make sense and/or may not
678 be understood by the web client's libraries.
679
680 Fix those here.
681
682 :param dict context: context to fix
683 """
684 lang = context['lang']
685
686 # inane OpenERP locale
687 if lang == 'ar_AR':
688 lang = 'ar'
689
690 # lang to lang_REGION (datejs only handles lang_REGION, no bare langs)
691 if lang in babel.core.LOCALE_ALIASES:
692 lang = babel.core.LOCALE_ALIASES[lang]
693
694 context['lang'] = lang or 'en_US'
695
696 """
697 Damn properties for retro-compatibility. All of that is deprecated, all
698 of that.
699 """
700 @property
701 def _db(self):
702 return self.db
703 @_db.setter
704 def _db(self, value):
705 self.db = value
706 @property
707 def _uid(self):
708 return self.uid
709 @_uid.setter
710 def _uid(self, value):
711 self.uid = value
712 @property
713 def _login(self):
714 return self.login
715 @_login.setter
716 def _login(self, value):
717 self.login = value
718 @property
719 def _password(self):
720 return self.password
721 @_password.setter
722 def _password(self, value):
723 self.password = value
724
725 def send(self, service_name, method, *args):
726 """
727 .. deprecated:: 8.0
728 Use ``openerp.netsvc.dispatch_rpc()`` instead.
729 """
730 return openerp.netsvc.dispatch_rpc(service_name, method, args)
731
732 def proxy(self, service):
733 """
734 .. deprecated:: 8.0
735 Use ``openerp.netsvc.dispatch_rpc()`` instead.
736 """
737 return Service(self, service)
738
739 def assert_valid(self, force=False):
740 """
741 .. deprecated:: 8.0
742 Use ``check_security()`` instead.
743
744 Ensures this session is valid (logged into the openerp server)
745 """
746 if self.uid and not force:
747 return
748 # TODO use authenticate instead of login
749 self.uid = self.proxy("common").login(self.db, self.login, self.password)
750 if not self.uid:
751 raise AuthenticationError("Authentication failure")
752
753 def ensure_valid(self):
754 """
755 .. deprecated:: 8.0
756 Use ``check_security()`` instead.
757 """
758 if self.uid:
759 try:
760 self.assert_valid(True)
761 except Exception:
762 self.uid = None
763
764 def execute(self, model, func, *l, **d):
765 """
766 .. deprecated:: 8.0
767 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
768 """
769 model = self.model(model)
770 r = getattr(model, func)(*l, **d)
771 return r
772
773 def exec_workflow(self, model, id, signal):
774 """
775 .. deprecated:: 8.0
776 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
777 """
778 self.assert_valid()
779 r = self.proxy('object').exec_workflow(self.db, self.uid, self.password, model, signal, id)
780 return r
781
782 def model(self, model):
783 """
784 .. deprecated:: 8.0
785 Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
786
787 Get an RPC proxy for the object ``model``, bound to this session.
788
789 :param model: an OpenERP model name
790 :type model: str
791 :rtype: a model object
792 """
793 if not self.db:
794 raise SessionExpiredException("Session expired")
795
796 return Model(self, model)
797
798def session_gc(session_store):
799 if random.random() < 0.001:
800 # we keep session one week
801 last_week = time.time() - 60*60*24*7
802 for fname in os.listdir(session_store.path):
803 path = os.path.join(session_store.path, fname)
804 try:
805 if os.path.getmtime(path) < last_week:
806 os.unlink(path)
807 except OSError:
808 pass
809
810#----------------------------------------------------------
811# WSGI Application
812#----------------------------------------------------------
813# Add potentially missing (older ubuntu) font mime types
814mimetypes.add_type('application/font-woff', '.woff')
815mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
816mimetypes.add_type('application/x-font-ttf', '.ttf')
817
818class DisableCacheMiddleware(object):
819 def __init__(self, app):
820 self.app = app
821 def __call__(self, environ, start_response):
822 def start_wrapped(status, headers):
823 referer = environ.get('HTTP_REFERER', '')
824 parsed = urlparse.urlparse(referer)
825 debug = parsed.query.count('debug') >= 1
826
827 new_headers = []
828 unwanted_keys = ['Last-Modified']
829 if debug:
830 new_headers = [('Cache-Control', 'no-cache')]
831 unwanted_keys += ['Expires', 'Etag', 'Cache-Control']
832
833 for k, v in headers:
834 if k not in unwanted_keys:
835 new_headers.append((k, v))
836
837 start_response(status, new_headers)
838 return self.app(environ, start_wrapped)
839
840def session_path():
841 try:
842 import pwd
843 username = pwd.getpwuid(os.geteuid()).pw_name
844 except ImportError:
845 try:
846 username = getpass.getuser()
847 except Exception:
848 username = "unknown"
849 path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
850 try:
851 os.mkdir(path, 0700)
852 except OSError as exc:
853 if exc.errno == errno.EEXIST:
854 # directory exists: ensure it has the correct permissions
855 # this will fail if the directory is not owned by the current user
856 os.chmod(path, 0700)
857 else:
858 raise
859 return path
860
861class Root(object):
862 """Root WSGI application for the OpenERP Web Client.
863 """
864 def __init__(self):
865 self.addons = {}
866 self.statics = {}
867
868 self.db_routers = {}
869 self.db_routers_lock = threading.Lock()
870
871 self.load_addons()
872
873 # Setup http sessions
874 path = session_path()
875 self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
876 _logger.debug('HTTP sessions stored in: %s', path)
877
878
879 def __call__(self, environ, start_response):
880 """ Handle a WSGI request
881 """
882 return self.dispatch(environ, start_response)
883
884 def dispatch(self, environ, start_response):
885 """
886 Performs the actual WSGI dispatching for the application.
887 """
888 try:
889 httprequest = werkzeug.wrappers.Request(environ)
890 httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict
891 httprequest.app = self
892
893 session_gc(self.session_store)
894
895 sid = httprequest.args.get('session_id')
896 explicit_session = True
897 if not sid:
898 sid = httprequest.headers.get("X-Openerp-Session-Id")
899 if not sid:
900 sid = httprequest.cookies.get('session_id')
901 explicit_session = False
902 if sid is None:
903 httprequest.session = self.session_store.new()
904 else:
905 httprequest.session = self.session_store.get(sid)
906
907 self._find_db(httprequest)
908
909 if not "lang" in httprequest.session.context:
910 lang = httprequest.accept_languages.best or "en_US"
911 lang = babel.core.LOCALE_ALIASES.get(lang, lang).replace('-', '_')
912 httprequest.session.context["lang"] = lang
913
914 request = self._build_request(httprequest)
915 db = request.db
916
917 if db:
918 updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db)
919 if updated:
920 with self.db_routers_lock:
921 del self.db_routers[db]
922
923 with set_request(request):
924 self.find_handler()
925 result = request.dispatch()
926
927 if db:
928 openerp.modules.registry.RegistryManager.signal_caches_change(db)
929
930 if isinstance(result, basestring):
931 headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
932 response = werkzeug.wrappers.Response(result, headers=headers)
933 else:
934 response = result
935
936 if httprequest.session.should_save:
937 self.session_store.save(httprequest.session)
938 if not explicit_session and hasattr(response, 'set_cookie'):
939 response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60)
940
941 return response(environ, start_response)
942 except werkzeug.exceptions.HTTPException, e:
943 return e(environ, start_response)
944
945 def _find_db(self, httprequest):
946 db = db_monodb(httprequest)
947 if db != httprequest.session.db:
948 httprequest.session.logout()
949 httprequest.session.db = db
950
951 def _build_request(self, httprequest):
952 if httprequest.args.get('jsonp'):
953 return JsonRequest(httprequest)
954
955 if httprequest.mimetype == "application/json":
956 return JsonRequest(httprequest)
957 else:
958 return HttpRequest(httprequest)
959
960 def load_addons(self):
961 """ Load all addons from addons patch containg static files and
962 controllers and configure them. """
963
964 for addons_path in openerp.modules.module.ad_paths:
965 for module in sorted(os.listdir(str(addons_path))):
966 if module not in addons_module:
967 manifest_path = os.path.join(addons_path, module, '__openerp__.py')
968 path_static = os.path.join(addons_path, module, 'static')
969 if os.path.isfile(manifest_path) and os.path.isdir(path_static):
970 manifest = ast.literal_eval(open(manifest_path).read())
971 manifest['addons_path'] = addons_path
972 _logger.debug("Loading %s", module)
973 if 'openerp.addons' in sys.modules:
974 m = __import__('openerp.addons.' + module)
975 else:
976 m = __import__(module)
977 addons_module[module] = m
978 addons_manifest[module] = manifest
979 self.statics['/%s/static' % module] = path_static
980
981 app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics)
982 self.dispatch = DisableCacheMiddleware(app)
983
984 def _build_router(self, db):
985 _logger.info("Generating routing configuration for database %s" % db)
986 routing_map = routing.Map()
987
988 def gen(modules, nodb_only):
989 for module in modules:
990 for v in controllers_per_module[module]:
991 cls = v[1]
992
993 subclasses = cls.__subclasses__()
994 subclasses = [c for c in subclasses if c.__module__.split(".")[:2] == ["openerp", "addons"] and \
995 cls.__module__.split(".")[2] in modules]
996 if subclasses:
997 name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
998 cls = type(name, tuple(reversed(subclasses)), {})
999
1000 o = cls()
1001 members = inspect.getmembers(o)
1002 for mk, mv in members:
1003 if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and \
1004 nodb_only == (getattr(mv, "auth", None) == "none"):
1005 function = (o.get_wrapped_method(mk), mv)
1006 for url in mv.routes:
1007 if getattr(mv, "combine", False):
1008 url = os.path.join(o._cp_path, url)
1009 if url.endswith("/") and len(url) > 1:
1010 url = url[: -1]
1011 routing_map.add(routing.Rule(url, endpoint=function))
1012
1013 modules_set = set(controllers_per_module.keys())
1014 modules_set -= set("web")
1015 # building all none methods
1016 gen(["web"] + sorted(modules_set), True)
1017 if not db:
1018 return routing_map
1019
1020 registry = openerp.modules.registry.RegistryManager.get(db)
1021 with registry.cursor() as cr:
1022 m = registry.get('ir.module.module')
1023 ids = m.search(cr, openerp.SUPERUSER_ID, [('state','=','installed')])
1024 installed = set([x['name'] for x in m.read(cr, 1, ids, ['name'])])
1025 modules_set = modules_set.intersection(set(installed))
1026 modules = ["web"] + sorted(modules_set)
1027 # building all other methods
1028 gen(["web"] + sorted(modules_set), False)
1029
1030 return routing_map
1031
1032 def get_db_router(self, db):
1033 with self.db_routers_lock:
1034 router = self.db_routers.get(db)
1035 if not router:
1036 router = self._build_router(db)
1037 with self.db_routers_lock:
1038 self.db_routers[db] = router
1039 return router
1040
1041 def find_handler(self):
1042 """
1043 Tries to discover the controller handling the request for the path specified in the request.
1044 """
1045 path = request.httprequest.path
1046 urls = self.get_db_router(request.db).bind("")
1047 matched, arguments = urls.match(path)
1048 arguments = dict([(k, v) for k, v in arguments.items() if not k.startswith("_ignored_")])
1049 func, original = matched
1050
1051 def nfunc(*args, **kwargs):
1052 kwargs.update(arguments)
1053 return func(*args, **kwargs)
1054
1055 request.func = nfunc
1056 request.auth_method = getattr(original, "auth", "user")
1057 request.func_request_type = original.exposed
1058
1059root = None
1060
1061def db_list(force=False, httprequest=None):
1062 httprequest = httprequest or request.httprequest
1063 dbs = openerp.netsvc.dispatch_rpc("db", "list", [force])
1064 h = httprequest.environ['HTTP_HOST'].split(':')[0]
1065 d = h.split('.')[0]
1066 r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
1067 dbs = [i for i in dbs if re.match(r, i)]
1068 return dbs
1069
1070def db_monodb(httprequest=None):
1071 """
1072 Magic function to find the current database.
1073
1074 Implementation details:
1075
1076 * Magic
1077 * More magic
1078
1079 Returns ``None`` if the magic is not magic enough.
1080 """
1081 httprequest = httprequest or request.httprequest
1082 db = None
1083 redirect = None
1084
1085 dbs = db_list(True, httprequest)
1086
1087 # 1 try the db already in the session
1088 db_session = httprequest.session.db
1089 if db_session in dbs:
1090 return db_session
1091
1092 # 2 if there is only one db in the db filters, take it
1093 if len(dbs) == 1:
1094 return dbs[0]
1095
1096 # 3 if there are multiple dbs, take the first one only if we can list them
1097 if len(dbs) > 1 and config['list_db']:
1098 return dbs[0]
1099 return None
1100
1101class CommonController(Controller):
1102
1103 @route('/jsonrpc', type='json', auth="none")
1104 def jsonrpc(self, service, method, args):
1105 """ Method used by client APIs to contact OpenERP. """
1106 return openerp.netsvc.dispatch_rpc(service, method, args)
1107
1108 @route('/gen_session_id', type='json', auth="none")
1109 def gen_session_id(self):
1110 nsession = root.session_store.new()
1111 return nsession.sid
1112
1113def wsgi_postload():
1114 global root
1115 root = Root()
1116 openerp.wsgi.register_wsgi_handler(root)
1117
1118# vim:et:ts=4:sw=4: