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