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, {
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)
144 view.model_data_id.write({
147 if view.model_data_id.module not in modules_to_update:
148 modules_to_update.append(view.model_data_id.module)
149 module_obj = request.registry['ir.module.module']
150 module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
151 module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
152 return request.redirect(redirect)
154 @http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
155 def customize_template_set(self, view_id):
156 view_obj = request.registry.get("ir.ui.view")
157 view = view_obj.browse(request.cr, request.uid, int(view_id),
158 context=request.context)
162 value = view.inherit_option_id and view.inherit_option_id.id or False
163 view_obj.write(request.cr, request.uid, [view_id], {
165 }, context=request.context)
168 @http.route('/website/customize_template_get', type='json', auth='user', website=True)
169 def customize_template_get(self, xml_id, optional=True):
170 imd = request.registry['ir.model.data']
171 view_model, view_theme_id = imd.get_object_reference(
172 request.cr, request.uid, 'website', 'theme')
174 user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context)
175 group_ids = [g.id for g in user.groups_id]
177 view = request.registry.get("ir.ui.view")
178 views = view._views_get(request.cr, request.uid, xml_id, context=request.context)
182 if v.groups_id and [g for g in v.groups_id if g.id not in group_ids]:
184 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
185 if v.inherit_option_id.id not in done:
187 'name': v.inherit_option_id.name,
190 'inherit_id': v.inherit_id.id,
194 done[v.inherit_option_id.id] = True
199 'inherit_id': v.inherit_id.id,
201 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
205 @http.route('/website/get_view_translations', type='json', auth='public', website=True)
206 def get_view_translations(self, xml_id, lang=None):
207 lang = lang or request.context.get('lang')
208 views = self.customize_template_get(xml_id, optional=False)
209 views_ids = [view.get('id') for view in views if view.get('active')]
210 domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
211 irt = request.registry.get('ir.translation')
212 return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
214 @http.route('/website/set_translations', type='json', auth='public', website=True)
215 def set_translations(self, data, lang):
216 irt = request.registry.get('ir.translation')
217 for view_id, trans in data.items():
218 view_id = int(view_id)
220 initial_content = t['initial_content'].strip()
221 new_content = t['new_content'].strip()
222 tid = t['translation_id']
224 old_trans = irt.search_read(
225 request.cr, request.uid,
227 ('type', '=', 'view'),
228 ('res_id', '=', view_id),
230 ('src', '=', initial_content),
233 tid = old_trans[0]['id']
235 vals = {'value': new_content}
236 irt.write(request.cr, request.uid, [tid], vals)
243 'source': initial_content,
244 'value': new_content,
246 irt.create(request.cr, request.uid, new_trans)
249 @http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
250 def attach(self, func, upload):
254 image_data = upload.read()
255 image = Image.open(cStringIO.StringIO(image_data))
257 if w*h > 42e6: # Nokia Lumia 1020 photo resolution
259 u"Image size excessive, uploaded images must be smaller "
260 u"than 42 million pixel")
262 attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
263 'name': upload.filename,
264 'datas': image_data.encode('base64'),
265 'datas_fname': upload.filename,
266 'res_model': 'ir.ui.view',
269 url = website.urlplus('/website/image', {
270 'model': 'ir.attachment',
273 'max_height': MAX_IMAGE_HEIGHT,
274 'max_width': MAX_IMAGE_WIDTH,
277 logger.exception("Failed to upload image to attachment")
280 return """<script type='text/javascript'>
281 window.parent['%s'](%s, %s);
282 </script>""" % (func, json.dumps(url), json.dumps(message))
284 @http.route(['/website/publish'], type='json', auth="public", website=True)
285 def publish(self, id, object):
287 _object = request.registry[object]
288 obj = _object.browse(request.cr, request.uid, _id)
291 if 'website_published' in _object._all_columns:
292 values['website_published'] = not obj.website_published
293 if 'website_published_datetime' in _object._all_columns and values.get('website_published'):
294 values['website_published_datetime'] = fields.datetime.now()
295 _object.write(request.cr, request.uid, [_id],
296 values, context=request.context)
298 obj = _object.browse(request.cr, request.uid, _id)
299 return bool(obj.website_published)
301 #------------------------------------------------------
303 #------------------------------------------------------
304 @http.route(['/website/kanban/'], type='http', auth="public", methods=['POST'], website=True)
305 def kanban(self, **post):
306 return request.website.kanban_col(**post)
308 def placeholder(self, response):
309 # file_open may return a StringIO. StringIO can be closed but are
310 # not context managers in Python 2 though that is fixed in 3
311 with contextlib.closing(openerp.tools.misc.file_open(
312 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
314 response.data = f.read()
315 return response.make_conditional(request.httprequest)
319 '/website/image/<model>/<id>/<field>'
320 ], auth="public", website=True)
321 def website_image(self, model, id, field, max_width=maxint, max_height=maxint):
322 Model = request.registry[model]
324 response = werkzeug.wrappers.Response()
328 ids = Model.search(request.cr, request.uid,
329 [('id', '=', id)], context=request.context) \
330 or Model.search(request.cr, openerp.SUPERUSER_ID,
331 [('id', '=', id), ('website_published', '=', True)], context=request.context)
334 return self.placeholder(response)
336 concurrency = '__last_update'
337 [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
338 [concurrency, field], context=request.context)
340 if concurrency in record:
341 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
343 response.last_modified = datetime.datetime.strptime(
344 record[concurrency], server_format + '.%f')
346 # just in case we have a timestamp without microseconds
347 response.last_modified = datetime.datetime.strptime(
348 record[concurrency], server_format)
350 # Field does not exist on model or field set to False
351 if not record.get(field):
352 # FIXME: maybe a field which does not exist should be a 404?
353 return self.placeholder(response)
355 response.set_etag(hashlib.sha1(record[field]).hexdigest())
356 response.make_conditional(request.httprequest)
358 # conditional request match
359 if response.status_code == 304:
362 data = record[field].decode('base64')
363 fit = int(max_width), int(max_height)
365 buf = cStringIO.StringIO(data)
367 image = Image.open(buf)
369 response.mimetype = Image.MIME[image.format]
374 if w < max_w and h < max_h:
377 image.thumbnail(fit, Image.ANTIALIAS)
378 image.save(response.stream, image.format)
379 # invalidate content-length computed by make_conditional as writing
380 # to response.stream does not do it (as of werkzeug 0.9.3)
381 del response.headers['Content-Length']
386 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: