1 # -*- coding: utf-8 -*-
10 from sys import maxint
14 import werkzeug.exceptions
16 import werkzeug.wrappers
20 from openerp.osv import fields
21 from openerp.addons.website.models import website
22 from openerp.addons.web import http
23 from openerp.addons.web.http import request, LazyResponse
25 logger = logging.getLogger(__name__)
27 # Completely arbitrary limits
28 MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
30 class Website(openerp.addons.web.controllers.main.Home):
31 @http.route('/', type='http', auth="public", website=True, multilang=True)
32 def index(self, **kw):
34 main_menu = request.registry['ir.model.data'].get_object(request.cr, request.uid, 'website', 'main_menu')
35 first_menu = main_menu.child_id and main_menu.child_id[0]
36 if first_menu and not ((first_menu.url == '/') or first_menu.url.startswith('/#') or first_menu.url.startswith('/?')):
37 return request.redirect(first_menu.url)
40 return self.page("website.homepage")
42 @http.route(website=True, auth="public", multilang=True)
43 def web_login(self, *args, **kw):
44 response = super(Website, self).web_login(*args, **kw)
45 if isinstance(response, LazyResponse):
46 values = dict(response.params['values'], disable_footer=True)
47 response = request.website.render(response.params['template'], values)
50 @http.route('/pagenew/<path:path>', type='http', auth="user", website=True)
51 def pagenew(self, path, noredirect=False):
52 xml_id = request.registry['website'].new_page(request.cr, request.uid, path, context=request.context)
53 url = "/page/" + xml_id
55 return werkzeug.wrappers.Response(url, mimetype='text/plain')
56 return werkzeug.utils.redirect(url)
58 @http.route('/website/theme_change', type='http', auth="user", website=True)
59 def theme_change(self, theme_id=False, **kwargs):
60 imd = request.registry['ir.model.data']
61 view = request.registry['ir.ui.view']
63 view_model, view_option_id = imd.get_object_reference(
64 request.cr, request.uid, 'website', 'theme')
66 request.cr, request.uid, [('inherit_id', '=', view_option_id)],
67 context=request.context)
68 view.write(request.cr, request.uid, views, {'inherit_id': False},
69 context=request.context)
72 module, xml_id = theme_id.split('.')
73 view_model, view_id = imd.get_object_reference(
74 request.cr, request.uid, module, xml_id)
75 view.write(request.cr, request.uid, [view_id],
76 {'inherit_id': view_option_id}, context=request.context)
78 return request.website.render('website.themes', {'theme_changed': True})
80 @http.route(['/website/snippets'], type='json', auth="public", website=True)
82 return request.website._render('website.snippets')
84 @http.route('/page/<page:page>', type='http', auth="public", website=True, multilang=True)
85 def page(self, page, **opt):
89 # allow shortcut for /page/<website_xml_id>
91 page = 'website.%s' % page
94 request.website.get_template(page)
97 if request.context['editable']:
98 page = 'website.page_404'
100 return request.registry['ir.http']._handle_exception(e, 404)
102 return request.website.render(page, values)
104 @http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True)
105 def reset_template(self, templates, redirect='/'):
106 templates = request.httprequest.form.getlist('templates')
107 modules_to_update = []
108 for temp_id in templates:
109 view = request.registry['ir.ui.view'].browse(request.cr, request.uid, int(temp_id), context=request.context)
110 view.model_data_id.write({
113 if view.model_data_id.module not in modules_to_update:
114 modules_to_update.append(view.model_data_id.module)
115 module_obj = request.registry['ir.module.module']
116 module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
117 module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
118 return request.redirect(redirect)
120 @http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
121 def customize_template_set(self, view_id):
122 view_obj = request.registry.get("ir.ui.view")
123 view = view_obj.browse(request.cr, request.uid, int(view_id),
124 context=request.context)
128 value = view.inherit_option_id and view.inherit_option_id.id or False
129 view_obj.write(request.cr, request.uid, [view_id], {
131 }, context=request.context)
134 @http.route('/website/customize_template_get', type='json', auth='user', website=True)
135 def customize_template_get(self, xml_id, optional=True):
136 imd = request.registry['ir.model.data']
137 view_model, view_theme_id = imd.get_object_reference(
138 request.cr, request.uid, 'website', 'theme')
140 user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context)
141 group_ids = [g.id for g in user.groups_id]
143 view = request.registry.get("ir.ui.view")
144 views = view._views_get(request.cr, request.uid, xml_id, request.context)
148 if v.groups_id and [g for g in v.groups_id if g.id not in group_ids]:
150 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
151 if v.inherit_option_id.id not in done:
153 'name': v.inherit_option_id.name,
155 'inherit_id': v.inherit_id.id,
159 done[v.inherit_option_id.id] = True
163 'inherit_id': v.inherit_id.id,
165 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
169 @http.route('/website/get_view_translations', type='json', auth='public', website=True)
170 def get_view_translations(self, xml_id, lang=None):
171 lang = lang or request.context.get('lang')
172 views = self.customize_template_get(xml_id, optional=False)
173 views_ids = [view.get('id') for view in views if view.get('active')]
174 domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
175 irt = request.registry.get('ir.translation')
176 return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
178 @http.route('/website/set_translations', type='json', auth='public', website=True)
179 def set_translations(self, data, lang):
180 irt = request.registry.get('ir.translation')
181 for view_id, trans in data.items():
182 view_id = int(view_id)
184 initial_content = t['initial_content'].strip()
185 new_content = t['new_content'].strip()
186 tid = t['translation_id']
188 old_trans = irt.search_read(
189 request.cr, request.uid,
191 ('type', '=', 'view'),
192 ('res_id', '=', view_id),
194 ('src', '=', initial_content),
197 tid = old_trans[0]['id']
199 vals = {'value': new_content}
200 irt.write(request.cr, request.uid, [tid], vals)
207 'source': initial_content,
208 'value': new_content,
210 irt.create(request.cr, request.uid, new_trans)
213 @http.route('/website/attach', type='http', auth='user', website=True)
214 def attach(self, func, upload):
215 req = request.httprequest
216 if req.method != 'POST':
217 return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
221 attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
222 'name': upload.filename,
223 'datas': upload.read().encode('base64'),
224 'datas_fname': upload.filename,
225 'res_model': 'ir.ui.view',
228 url = website.urlplus('/website/image', {
229 'model': 'ir.attachment',
232 'max_height': MAX_IMAGE_HEIGHT,
233 'max_width': MAX_IMAGE_WIDTH,
236 logger.exception("Failed to upload image to attachment")
239 return """<script type='text/javascript'>
240 window.parent['%s'](%s, %s);
241 </script>""" % (func, json.dumps(url), json.dumps(message))
243 @http.route(['/website/publish'], type='json', auth="public", website=True)
244 def publish(self, id, object):
246 _object = request.registry[object]
247 obj = _object.browse(request.cr, request.uid, _id)
250 if 'website_published' in _object._all_columns:
251 values['website_published'] = not obj.website_published
252 if 'website_published_datetime' in _object._all_columns and values.get('website_published'):
253 values['website_published_datetime'] = fields.datetime.now()
254 _object.write(request.cr, request.uid, [_id],
255 values, context=request.context)
257 obj = _object.browse(request.cr, request.uid, _id)
258 return bool(obj.website_published)
260 @http.route(['/website/kanban/'], type='http', auth="public", methods=['POST'], website=True)
261 def kanban(self, **post):
262 return request.website.kanban_col(**post)
264 @http.route(['/robots.txt'], type='http', auth="public", website=True)
266 response = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
267 response.mimetype = 'text/plain'
270 @http.route('/sitemap', type='http', auth='public', website=True, multilang=True)
272 return request.website.render('website.sitemap', {
273 'pages': request.website.enumerate_pages()
276 @http.route('/sitemap.xml', type='http', auth="public", website=True)
277 def sitemap_xml(self):
278 response = request.website.render('website.sitemap_xml', {
279 'pages': request.website.enumerate_pages()
281 response.headers['Content-Type'] = 'application/xml;charset=utf-8'
284 class Images(http.Controller):
285 def placeholder(self, response):
286 # file_open may return a StringIO. StringIO can be closed but are
287 # not context managers in Python 2 though that is fixed in 3
288 with contextlib.closing(openerp.tools.misc.file_open(
289 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
291 response.set_data(f.read())
292 return response.make_conditional(request.httprequest)
294 @http.route('/website/image', auth="public", website=True)
295 def image(self, model, id, field, max_width=maxint, max_height=maxint):
296 Model = request.registry[model]
298 response = werkzeug.wrappers.Response()
302 ids = Model.search(request.cr, request.uid,
303 [('id', '=', id)], context=request.context) \
304 or Model.search(request.cr, openerp.SUPERUSER_ID,
305 [('id', '=', id), ('website_published', '=', True)], context=request.context)
308 return self.placeholder(response)
310 concurrency = '__last_update'
311 [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
312 [concurrency, field], context=request.context)
314 if concurrency in record:
315 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
317 response.last_modified = datetime.datetime.strptime(
318 record[concurrency], server_format + '.%f')
320 # just in case we have a timestamp without microseconds
321 response.last_modified = datetime.datetime.strptime(
322 record[concurrency], server_format)
324 # Field does not exist on model or field set to False
325 if not record.get(field):
326 # FIXME: maybe a field which does not exist should be a 404?
327 return self.placeholder(response)
329 response.set_etag(hashlib.sha1(record[field]).hexdigest())
330 response.make_conditional(request.httprequest)
332 # conditional request match
333 if response.status_code == 304:
336 data = record[field].decode('base64')
337 fit = int(max_width), int(max_height)
339 buf = cStringIO.StringIO(data)
341 image = Image.open(buf)
343 response.mimetype = Image.MIME[image.format]
348 if w < max_w and h < max_h:
349 response.set_data(data)
351 image.thumbnail(fit, Image.ANTIALIAS)
352 image.save(response.stream, image.format)
353 # invalidate content-length computed by make_conditional as writing
354 # to response.stream does not do it (as of werkzeug 0.9.3)
355 del response.headers['Content-Length']
359 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: