[ADD] doc: new documentation, with training tutorials, and new scaffolding
[odoo/odoo.git] / openerp / addons / base / ir / ir_http.py
1 #----------------------------------------------------------
2 # ir_http modular http routing
3 #----------------------------------------------------------
4 import logging
5 import re
6 import sys
7
8 import werkzeug.exceptions
9 import werkzeug.routing
10 import werkzeug.urls
11 import werkzeug.utils
12
13 import openerp
14 import openerp.exceptions
15 import openerp.models
16 from openerp import http
17 from openerp.http import request
18 from openerp.osv import osv, orm
19
20 _logger = logging.getLogger(__name__)
21
22 UID_PLACEHOLDER = object()
23
24 class ModelConverter(werkzeug.routing.BaseConverter):
25
26     def __init__(self, url_map, model=False):
27         super(ModelConverter, self).__init__(url_map)
28         self.model = model
29         self.regex = '([0-9]+)'
30
31     def to_python(self, value):
32         m = re.match(self.regex, value)
33         return request.registry[self.model].browse(
34             request.cr, UID_PLACEHOLDER, int(m.group(1)), context=request.context)
35
36     def to_url(self, value):
37         return value.id
38
39 class ModelsConverter(werkzeug.routing.BaseConverter):
40
41     def __init__(self, url_map, model=False):
42         super(ModelsConverter, self).__init__(url_map)
43         self.model = model
44         # TODO add support for slug in the form [A-Za-z0-9-] bla-bla-89 -> id 89
45         self.regex = '([0-9,]+)'
46
47     def to_python(self, value):
48         return request.registry[self.model].browse(request.cr, UID_PLACEHOLDER, [int(i) for i in value.split(',')], context=request.context)
49
50     def to_url(self, value):
51         return ",".join(i.id for i in value)
52
53 class ir_http(osv.AbstractModel):
54     _name = 'ir.http'
55     _description = "HTTP routing"
56
57     def _get_converters(self):
58         return {'model': ModelConverter, 'models': ModelsConverter}
59
60     def _find_handler(self, return_rule=False):
61         return self.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule)
62
63     def _auth_method_user(self):
64         request.uid = request.session.uid
65         if not request.uid:
66             if not request.params.get('noredirect'):
67                 query = werkzeug.urls.url_encode({
68                     'redirect': request.httprequest.url,
69                 })
70                 response = werkzeug.utils.redirect('/web/login?%s' % query)
71                 werkzeug.exceptions.abort(response)
72             raise http.SessionExpiredException("Session expired")
73
74     def _auth_method_none(self):
75         request.uid = None
76
77     def _auth_method_public(self):
78         if not request.session.uid:
79             dummy, request.uid = self.pool['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
80         else:
81             request.uid = request.session.uid
82
83     def _authenticate(self, auth_method='user'):
84         try:
85             if request.session.uid:
86                 try:
87                     request.session.check_security()
88                     # what if error in security.check()
89                     #   -> res_users.check()
90                     #   -> res_users.check_credentials()
91                 except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
92                     # All other exceptions mean undetermined status (e.g. connection pool full),
93                     # let them bubble up
94                     request.session.logout()
95             getattr(self, "_auth_method_%s" % auth_method)()
96         except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
97             raise
98         except Exception:
99             _logger.exception("Exception during request Authentication.")
100             raise openerp.exceptions.AccessDenied()
101         return auth_method
102
103     def _handle_exception(self, exception):
104         # If handle_exception returns something different than None, it will be used as a response
105         return request._handle_exception(exception)
106
107     def _dispatch(self):
108         # locate the controller method
109         try:
110             rule, arguments = self._find_handler(return_rule=True)
111             func = rule.endpoint
112         except werkzeug.exceptions.NotFound, e:
113             return self._handle_exception(e)
114
115         # check authentication level
116         try:
117             auth_method = self._authenticate(func.routing["auth"])
118         except Exception:
119             # force a Forbidden exception with the original traceback
120             return self._handle_exception(
121                 convert_exception_to(
122                     werkzeug.exceptions.Forbidden))
123
124         processing = self._postprocess_args(arguments, rule)
125         if processing:
126             return processing
127
128
129         # set and execute handler
130         try:
131             request.set_handler(func, arguments, auth_method)
132             result = request.dispatch()
133             if isinstance(result, Exception):
134                 raise result
135         except Exception, e:
136             return self._handle_exception(e)
137
138         return result
139
140     def _postprocess_args(self, arguments, rule):
141         """ post process arg to set uid on browse records """
142         for name, arg in arguments.items():
143             if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER:
144                 arguments[name] = arg.sudo(request.uid)
145                 try:
146                     arg.exists()
147                 except openerp.models.MissingError:
148                     return self._handle_exception(werkzeug.exceptions.NotFound())
149
150     def routing_map(self):
151         if not hasattr(self, '_routing_map'):
152             _logger.info("Generating routing map")
153             cr = request.cr
154             m = request.registry.get('ir.module.module')
155             ids = m.search(cr, openerp.SUPERUSER_ID, [('state', '=', 'installed'), ('name', '!=', 'web')], context=request.context)
156             installed = set(x['name'] for x in m.read(cr, 1, ids, ['name'], context=request.context))
157             if openerp.tools.config['test_enable']:
158                 installed.add(openerp.modules.module.current_test)
159             mods = [''] + openerp.conf.server_wide_modules + sorted(installed)
160             self._routing_map = http.routing_map(mods, False, converters=self._get_converters())
161
162         return self._routing_map
163
164 def convert_exception_to(to_type, with_message=False):
165     """ Should only be called from an exception handler. Fetches the current
166     exception data from sys.exc_info() and creates a new exception of type
167     ``to_type`` with the original traceback.
168
169     If ``with_message`` is ``True``, sets the new exception's message to be
170     the stringification of the original exception. If ``False``, does not
171     set the new exception's message. Otherwise, uses ``with_message`` as the
172     new exception's message.
173
174     :type with_message: str|bool
175     """
176     etype, original, tb = sys.exc_info()
177     try:
178         if with_message is False:
179             message = None
180         elif with_message is True:
181             message = str(original)
182         else:
183             message = str(with_message)
184
185         raise to_type, message, tb
186     except to_type, e:
187         return e
188
189 # vim:et: