1 # -*- coding: utf-8 -*-
8 import werkzeug.routing
11 from openerp.addons.base import ir
12 from openerp.addons.base.ir import ir_qweb
13 from openerp.addons.website.models.website import slug
14 from openerp.http import request
15 from openerp.osv import orm
17 logger = logging.getLogger(__name__)
19 class RequestUID(object):
20 def __init__(self, **kw):
21 self.__dict__.update(kw)
23 class ir_http(orm.AbstractModel):
28 def _get_converters(self):
30 super(ir_http, self)._get_converters(),
36 first_pass = not hasattr(request, 'website')
37 request.website = None
40 func, arguments = self._find_handler()
41 request.website_enabled = func.routing.get('website', False)
42 except werkzeug.exceptions.NotFound:
43 # either we have a language prefixed route, either a real 404
44 # in all cases, website processes them
45 request.website_enabled = True
47 if request.website_enabled:
49 self._authenticate(func.routing['auth'])
51 self._auth_method_public()
52 request.website = request.registry['website'].get_current_website(request.cr, request.uid, context=request.context)
54 request.lang = request.website.default_lang_code
55 request.context['lang'] = request.lang
56 request.website.preprocess_request(request)
58 path = request.httprequest.path.split('/')
59 langs = [lg[0] for lg in request.website.get_languages()]
61 request.lang = request.context['lang'] = path.pop(1)
62 path = '/'.join(path) or '/'
63 return self.reroute(path)
64 return self._handle_exception(code=404)
65 return super(ir_http, self)._dispatch()
67 def reroute(self, path):
68 if not hasattr(request, 'rerouting'):
69 request.rerouting = [request.httprequest.path]
70 if path in request.rerouting:
71 raise Exception("Rerouting loop is forbidden")
72 request.rerouting.append(path)
73 if len(request.rerouting) > self.rerouting_limit:
74 raise Exception("Rerouting limit exceeded")
75 request.httprequest.environ['PATH_INFO'] = path
76 # void werkzeug cached_property. TODO: find a proper way to do this
77 for key in ('path', 'full_path', 'url', 'base_url'):
78 request.httprequest.__dict__.pop(key, None)
80 return self._dispatch()
82 def _postprocess_args(self, arguments, rule):
83 if not getattr(request, 'website_enabled', False):
84 return super(ir_http, self)._postprocess_args(arguments, rule)
86 for arg, val in arguments.items():
87 # Replace uid placeholder by the current request.uid
88 if isinstance(val, orm.browse_record) and isinstance(val._uid, RequestUID):
89 val._uid = request.uid
91 _, path = rule.build(arguments)
92 assert path is not None
94 return self._handle_exception(werkzeug.exceptions.NotFound())
96 if request.httprequest.method in ('GET', 'HEAD'):
97 generated_path = werkzeug.url_unquote_plus(path)
98 current_path = werkzeug.url_unquote_plus(request.httprequest.path)
99 if generated_path != current_path:
100 if request.lang != request.website.default_lang_code:
101 path = '/' + request.lang + path
102 return werkzeug.utils.redirect(path)
104 def _serve_attachment(self):
105 domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
106 attach = self.pool['ir.attachment'].search_read(request.cr, openerp.SUPERUSER_ID, domain, ['__last_update', 'datas', 'mimetype'], context=request.context)
108 wdate = attach[0]['__last_update']
109 datas = attach[0]['datas']
110 response = werkzeug.wrappers.Response()
111 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
113 response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
115 # just in case we have a timestamp without microseconds
116 response.last_modified = datetime.datetime.strptime(wdate, server_format)
118 response.set_etag(hashlib.sha1(datas).hexdigest())
119 response.make_conditional(request.httprequest)
121 if response.status_code == 304:
124 response.mimetype = attach[0]['mimetype']
125 response.data = datas.decode('base64')
128 def _handle_exception(self, exception=None, code=500):
130 return super(ir_http, self)._handle_exception(exception)
133 attach = self._serve_attachment()
137 if getattr(request, 'website_enabled', False) and request.website:
140 traceback=traceback.format_exc(exception),
143 code = getattr(exception, 'code', code)
144 if isinstance(exception, ir_qweb.QWebException):
145 values.update(qweb_exception=exception)
146 if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
149 logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
150 if 'qweb_exception' in values:
151 view = request.registry.get("ir.ui.view")
152 views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
153 to_reset = [v for v in views if v.model_data_id.noupdate is True]
154 values['views'] = to_reset
156 logger.warn("403 Forbidden:\n\n%s", values['traceback'])
159 status_message=werkzeug.http.HTTP_STATUS_CODES[code],
164 self._auth_method_public()
167 html = request.website._render('website.%s' % code, values)
169 html = request.website._render('website.http_error', values)
170 return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
174 class ModelConverter(ir.ir_http.ModelConverter):
175 def __init__(self, url_map, model=False, domain='[]'):
176 super(ModelConverter, self).__init__(url_map, model)
178 self.regex = r'(?:[A-Za-z0-9-_]+?-)?(\d+)(?=$|/)'
180 def to_url(self, value):
183 def to_python(self, value):
184 m = re.match(self.regex, value)
185 _uid = RequestUID(value=value, match=m, converter=self)
186 return request.registry[self.model].browse(
187 request.cr, _uid, int(m.group(1)), context=request.context)
189 def generate(self, cr, uid, query=None, args=None, context=None):
190 obj = request.registry[self.model]
191 domain = eval( self.domain, (args or {}).copy())
193 domain.append((obj._rec_name, 'ilike', '%'+query+'%'))
194 for record in obj.search_read(cr, uid, domain=domain, fields=['write_date',obj._rec_name], context=context):
195 if record.get(obj._rec_name, False):
196 yield {'loc': (record['id'], record[obj._rec_name])}
198 class PageConverter(werkzeug.routing.PathConverter):
199 """ Only point of this converter is to bundle pages enumeration logic """
200 def generate(self, cr, uid, query=None, args={}, context=None):
201 View = request.registry['ir.ui.view']
202 views = View.search_read(cr, uid, [['page', '=', True]],
203 fields=['xml_id','priority','write_date'], order='name', context=context)
205 xid = view['xml_id'].startswith('website.') and view['xml_id'][8:] or view['xml_id']
206 # the 'page/homepage' url is indexed as '/', avoid aving the same page referenced twice
207 # when we will have an url mapping mechanism, replace this by a rule: page/homepage --> /
208 if xid=='homepage': continue
209 if query and query.lower() not in xid.lower():
211 record = {'loc': xid}
212 if view['priority'] <> 16:
213 record['__priority'] = min(round(view['priority'] / 32.0,1), 1)
214 if view.get('write_date'):
215 record['__lastmod'] = view['write_date'][:10]