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 #------------------------------------------------------
33 #------------------------------------------------------
34 @http.route('/', type='http', auth="public", website=True, multilang=True)
35 def index(self, **kw):
37 main_menu = request.registry['ir.model.data'].get_object(request.cr, request.uid, 'website', 'main_menu')
38 first_menu = main_menu.child_id and main_menu.child_id[0]
40 if first_menu and not ((first_menu.url == '/') or first_menu.url.startswith('/#') or first_menu.url.startswith('/?')):
41 return request.redirect(first_menu.url)
44 return self.page("website.homepage")
46 @http.route(website=True, auth="public", multilang=True)
47 def web_login(self, *args, **kw):
48 response = super(Website, self).web_login(*args, **kw)
49 if isinstance(response, LazyResponse):
50 values = dict(response.params['values'], disable_footer=True)
51 response = request.website.render(response.params['template'], values)
54 @http.route('/page/<page:page>', type='http', auth="public", website=True, multilang=True)
55 def page(self, page, **opt):
59 # allow shortcut for /page/<website_xml_id>
61 page = 'website.%s' % page
64 request.website.get_template(page)
67 if request.context['editable']:
68 page = 'website.page_404'
70 return request.registry['ir.http']._handle_exception(e, 404)
72 return request.website.render(page, values)
74 @http.route(['/robots.txt'], type='http', auth="public", website=True)
76 response = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
77 response.mimetype = 'text/plain'
80 @http.route('/sitemap', type='http', auth='public', website=True, multilang=True)
82 return request.website.render('website.sitemap', {
83 'pages': request.website.enumerate_pages()
86 @http.route('/sitemap.xml', type='http', auth="public", website=True)
87 def sitemap_xml(self):
88 response = request.website.render('website.sitemap_xml', {
89 'pages': request.website.enumerate_pages()
91 response.headers['Content-Type'] = 'application/xml;charset=utf-8'
94 #------------------------------------------------------
96 #------------------------------------------------------
97 @http.route('/website/add/<path:path>', type='http', auth="user", website=True)
98 def pagenew(self, path, noredirect=False, add_menu=None):
99 xml_id = request.registry['website'].new_page(request.cr, request.uid, path, context=request.context)
101 model, id = request.registry["ir.model.data"].get_object_reference(request.cr, request.uid, 'website', 'main_menu')
102 request.registry['website.menu'].create(request.cr, request.uid, {
104 'url': "/page/" + xml_id,
106 }, context=request.context)
107 url = "/page/" + xml_id
109 return werkzeug.wrappers.Response(url, mimetype='text/plain')
110 return werkzeug.utils.redirect(url)
112 @http.route('/website/theme_change', type='http', auth="user", website=True)
113 def theme_change(self, theme_id=False, **kwargs):
114 imd = request.registry['ir.model.data']
115 view = request.registry['ir.ui.view']
117 view_model, view_option_id = imd.get_object_reference(
118 request.cr, request.uid, 'website', 'theme')
120 request.cr, request.uid, [('inherit_id', '=', view_option_id)],
121 context=request.context)
122 view.write(request.cr, request.uid, views, {'inherit_id': False},
123 context=request.context)
126 module, xml_id = theme_id.split('.')
127 view_model, view_id = imd.get_object_reference(
128 request.cr, request.uid, module, xml_id)
129 view.write(request.cr, request.uid, [view_id],
130 {'inherit_id': view_option_id}, context=request.context)
132 return request.website.render('website.themes', {'theme_changed': True})
134 @http.route(['/website/snippets'], type='json', auth="public", website=True)
136 return request.website._render('website.snippets')
138 @http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True)
139 def reset_template(self, templates, redirect='/'):
140 templates = request.httprequest.form.getlist('templates')
141 modules_to_update = []
142 for temp_id in templates:
143 view = request.registry['ir.ui.view'].browse(request.cr, request.uid, int(temp_id), context=request.context)
146 view.model_data_id.write({
149 if view.model_data_id.module not in modules_to_update:
150 modules_to_update.append(view.model_data_id.module)
152 if modules_to_update:
153 module_obj = request.registry['ir.module.module']
154 module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
156 module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
157 return request.redirect(redirect)
159 @http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
160 def customize_template_set(self, view_id):
161 view_obj = request.registry.get("ir.ui.view")
162 view = view_obj.browse(request.cr, request.uid, int(view_id),
163 context=request.context)
167 value = view.inherit_option_id and view.inherit_option_id.id or False
168 view_obj.write(request.cr, request.uid, [view_id], {
170 }, context=request.context)
173 @http.route('/website/customize_template_get', type='json', auth='user', website=True)
174 def customize_template_get(self, xml_id, optional=True):
175 imd = request.registry['ir.model.data']
176 view_model, view_theme_id = imd.get_object_reference(
177 request.cr, request.uid, 'website', 'theme')
179 user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context)
180 group_ids = [g.id for g in user.groups_id]
182 view = request.registry.get("ir.ui.view")
183 views = view._views_get(request.cr, request.uid, xml_id, context=request.context)
187 if v.groups_id and [g for g in v.groups_id if g.id not in group_ids]:
189 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
190 if v.inherit_option_id.id not in done:
192 'name': v.inherit_option_id.name,
195 'inherit_id': v.inherit_id.id,
199 done[v.inherit_option_id.id] = True
204 'inherit_id': v.inherit_id.id,
206 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
210 @http.route('/website/get_view_translations', type='json', auth='public', website=True)
211 def get_view_translations(self, xml_id, lang=None):
212 lang = lang or request.context.get('lang')
213 views = self.customize_template_get(xml_id, optional=False)
214 views_ids = [view.get('id') for view in views if view.get('active')]
215 domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
216 irt = request.registry.get('ir.translation')
217 return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
219 @http.route('/website/set_translations', type='json', auth='public', website=True)
220 def set_translations(self, data, lang):
221 irt = request.registry.get('ir.translation')
222 for view_id, trans in data.items():
223 view_id = int(view_id)
225 initial_content = t['initial_content'].strip()
226 new_content = t['new_content'].strip()
227 tid = t['translation_id']
229 old_trans = irt.search_read(
230 request.cr, request.uid,
232 ('type', '=', 'view'),
233 ('res_id', '=', view_id),
235 ('src', '=', initial_content),
238 tid = old_trans[0]['id']
240 vals = {'value': new_content}
241 irt.write(request.cr, request.uid, [tid], vals)
248 'source': initial_content,
249 'value': new_content,
251 irt.create(request.cr, request.uid, new_trans)
254 @http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
255 def attach(self, func, upload):
259 image_data = upload.read()
260 image = Image.open(cStringIO.StringIO(image_data))
262 if w*h > 42e6: # Nokia Lumia 1020 photo resolution
264 u"Image size excessive, uploaded images must be smaller "
265 u"than 42 million pixel")
267 Attachments = request.registry['ir.attachment']
268 attachment_id = Attachments.create(request.cr, request.uid, {
269 'name': upload.filename,
270 'datas': image_data.encode('base64'),
271 'datas_fname': upload.filename,
272 'res_model': 'ir.ui.view',
275 [attachment] = Attachments.read(
276 request.cr, request.uid, [attachment_id], ['website_url'],
277 context=request.context)
278 url = attachment['website_url']
280 logger.exception("Failed to upload image to attachment")
283 return """<script type='text/javascript'>
284 window.parent['%s'](%s, %s);
285 </script>""" % (func, json.dumps(url), json.dumps(message))
287 @http.route(['/website/publish'], type='json', auth="public", website=True)
288 def publish(self, id, object):
290 _object = request.registry[object]
291 obj = _object.browse(request.cr, request.uid, _id)
294 if 'website_published' in _object._all_columns:
295 values['website_published'] = not obj.website_published
296 if 'website_published_datetime' in _object._all_columns and values.get('website_published'):
297 values['website_published_datetime'] = fields.datetime.now()
298 _object.write(request.cr, request.uid, [_id],
299 values, context=request.context)
301 obj = _object.browse(request.cr, request.uid, _id)
302 return bool(obj.website_published)
304 #------------------------------------------------------
306 #------------------------------------------------------
307 @http.route(['/website/kanban/'], type='http', auth="public", methods=['POST'], website=True)
308 def kanban(self, **post):
309 return request.website.kanban_col(**post)
311 def placeholder(self, response):
312 # file_open may return a StringIO. StringIO can be closed but are
313 # not context managers in Python 2 though that is fixed in 3
314 with contextlib.closing(openerp.tools.misc.file_open(
315 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
317 response.data = f.read()
318 return response.make_conditional(request.httprequest)
322 '/website/image/<model>/<id>/<field>'
323 ], auth="public", website=True)
324 def website_image(self, model, id, field, max_width=maxint, max_height=maxint):
325 Model = request.registry[model]
327 response = werkzeug.wrappers.Response()
331 ids = Model.search(request.cr, request.uid,
332 [('id', '=', id)], context=request.context) \
333 or Model.search(request.cr, openerp.SUPERUSER_ID,
334 [('id', '=', id), ('website_published', '=', True)], context=request.context)
337 return self.placeholder(response)
339 concurrency = '__last_update'
340 [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
341 [concurrency, field], context=request.context)
343 if concurrency in record:
344 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
346 response.last_modified = datetime.datetime.strptime(
347 record[concurrency], server_format + '.%f')
349 # just in case we have a timestamp without microseconds
350 response.last_modified = datetime.datetime.strptime(
351 record[concurrency], server_format)
353 # Field does not exist on model or field set to False
354 if not record.get(field):
355 # FIXME: maybe a field which does not exist should be a 404?
356 return self.placeholder(response)
358 response.set_etag(hashlib.sha1(record[field]).hexdigest())
359 response.make_conditional(request.httprequest)
361 # conditional request match
362 if response.status_code == 304:
365 data = record[field].decode('base64')
366 fit = int(max_width), int(max_height)
368 buf = cStringIO.StringIO(data)
370 image = Image.open(buf)
372 response.mimetype = Image.MIME[image.format]
377 if w < max_w and h < max_h:
380 image.thumbnail(fit, Image.ANTIALIAS)
381 image.save(response.stream, image.format)
382 # invalidate content-length computed by make_conditional as writing
383 # to response.stream does not do it (as of werkzeug 0.9.3)
384 del response.headers['Content-Length']
389 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: