1 # -*- coding: utf-8 -*-
7 import werkzeug.routing
10 from openerp.addons.base import ir
11 from openerp.addons.base.ir import ir_qweb
12 from openerp.addons.website.models.website import slug
13 from openerp.http import request
14 from openerp.osv import orm
16 logger = logging.getLogger(__name__)
18 class RequestUID(object):
19 def __init__(self, **kw):
20 self.__dict__.update(kw)
22 class ir_http(orm.AbstractModel):
27 def _get_converters(self):
29 super(ir_http, self)._get_converters(),
35 first_pass = not hasattr(request, 'website')
36 request.website = None
39 func, arguments = self._find_handler()
40 request.website_enabled = func.routing.get('website', False)
41 except werkzeug.exceptions.NotFound:
42 # either we have a language prefixed route, either a real 404
43 # in all cases, website processes them
44 request.website_enabled = True
46 if request.website_enabled:
48 self._authenticate(func.routing['auth'])
50 self._auth_method_public()
51 request.website = request.registry['website'].get_current_website(request.cr, request.uid, context=request.context)
53 request.lang = request.website.default_lang_code
54 request.context['lang'] = request.lang
55 request.website.preprocess_request(request)
57 path = request.httprequest.path.split('/')
58 langs = [lg[0] for lg in request.website.get_languages()]
60 request.lang = request.context['lang'] = path.pop(1)
61 path = '/'.join(path) or '/'
62 return self.reroute(path)
63 return super(ir_http, self)._dispatch()
65 def reroute(self, path):
66 if not hasattr(request, 'rerouting'):
67 request.rerouting = [request.httprequest.path]
68 if path in request.rerouting:
69 raise Exception("Rerouting loop is forbidden")
70 request.rerouting.append(path)
71 if len(request.rerouting) > self.rerouting_limit:
72 raise Exception("Rerouting limit exceeded")
73 request.httprequest.environ['PATH_INFO'] = path
74 # void werkzeug cached_property. TODO: find a proper way to do this
75 for key in ('path', 'full_path', 'url', 'base_url'):
76 request.httprequest.__dict__.pop(key, None)
78 return self._dispatch()
80 def _postprocess_args(self, arguments):
81 if hasattr(request, 'rerouting'):
82 url = request.rerouting[0]
84 url = request.httprequest.url
86 for arg in arguments.itervalues():
87 if isinstance(arg, orm.browse_record) and isinstance(arg._uid, RequestUID):
88 placeholder = arg._uid
89 arg._uid = request.uid
92 if str(arg.id) != placeholder.value and placeholder.value != good_slug:
93 # TODO: properly recompose the url instead of using replace()
94 url = url.replace(placeholder.value, good_slug)
96 return self._handle_exception(e, code=404)
97 if url != original_url:
98 werkzeug.exceptions.abort(werkzeug.utils.redirect(url))
100 def _handle_exception(self, exception, code=500):
101 is_website_request = bool(getattr(request, 'website_enabled', False) and request.website)
102 if not is_website_request:
103 # Don't touch non website requests exception handling
104 return super(ir_http, self)._handle_exception(exception)
107 response = super(ir_http, self)._handle_exception(exception)
108 if isinstance(response, Exception):
111 # if parent excplicitely returns a plain response, then we don't touch it
118 traceback=traceback.format_exc(exception),
120 code = getattr(exception, 'code', code)
122 if isinstance(exception, openerp.exceptions.AccessError):
125 if isinstance(exception, ir_qweb.QWebException):
126 values.update(qweb_exception=exception)
127 if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
130 if isinstance(exception, werkzeug.exceptions.HTTPException) and code is None:
131 # Hand-crafted HTTPException likely coming from abort(),
132 # usually for a redirect response -> return it directly
136 logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
137 if 'qweb_exception' in values:
138 view = request.registry.get("ir.ui.view")
139 views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
140 to_reset = [v for v in views if v.model_data_id.noupdate is True and not v.page]
141 values['views'] = to_reset
143 logger.warn("403 Forbidden:\n\n%s", values['traceback'])
146 status_message=werkzeug.http.HTTP_STATUS_CODES[code],
151 self._auth_method_public()
154 html = request.website._render('website.%s' % code, values)
156 html = request.website._render('website.http_error', values)
157 return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
159 class ModelConverter(ir.ir_http.ModelConverter):
160 def __init__(self, url_map, model=False):
161 super(ModelConverter, self).__init__(url_map, model)
162 self.regex = r'(?:[A-Za-z0-9-_]+?-)?(\d+)(?=$|/)'
164 def to_url(self, value):
167 def to_python(self, value):
168 m = re.match(self.regex, value)
169 _uid = RequestUID(value=value, match=m, converter=self)
170 return request.registry[self.model].browse(
171 request.cr, _uid, int(m.group(1)), context=request.context)
173 def generate(self, cr, uid, query=None, context=None):
174 return request.registry[self.model].name_search(
175 cr, uid, name=query or '', context=context)
177 class PageConverter(werkzeug.routing.PathConverter):
178 """ Only point of this converter is to bundle pages enumeration logic
180 Sads got: no way to get the view's human-readable name even if one exists
182 def generate(self, cr, uid, query=None, context=None):
183 View = request.registry['ir.ui.view']
184 views = View.search_read(
185 cr, uid, [['page', '=', True]],
186 fields=[], order='name', context=context)
187 xids = View.get_external_id(
188 cr, uid, [view['id'] for view in views], context=context)
191 xid = xids[view['id']]
192 if xid and (not query or query.lower() in xid.lower()):