1 # -*- coding: utf-8 -*-
11 from sys import maxint
15 import werkzeug.exceptions
17 import werkzeug.wrappers
21 from openerp.addons.website.models import website
22 from openerp.addons.web import http
23 from openerp.addons.web.http import request
25 logger = logging.getLogger(__name__)
28 def auth_method_public():
29 registry = openerp.modules.registry.RegistryManager.get(request.db)
30 if not request.session.uid:
31 request.uid = registry['website'].get_public_user().id
33 request.uid = request.session.uid
34 http.auth_methods['public'] = auth_method_public
37 # PIL images have a type flag, but no MIME. Reverse type flag to MIME.
38 PIL_MIME_MAPPING = {'PNG': 'image/png', 'JPEG': 'image/jpeg', 'GIF': 'image/gif', }
39 # Completely arbitrary limits
40 MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
41 class Website(openerp.addons.web.controllers.main.Home):
42 @website.route('/', type='http', auth="public", multilang=True)
43 def index(self, **kw):
44 return self.page("website.homepage")
46 @http.route('/admin', type='http', auth="none")
47 def admin(self, *args, **kw):
48 return super(Website, self).index(*args, **kw)
50 # FIXME: auth, if /pagenew known anybody can create new empty page
51 @website.route('/pagenew/<path:path>', type='http', auth="admin")
52 def pagenew(self, path, noredirect=NOPE):
54 module, idname = path.split('.', 1)
58 xid = "%s.%s" % (module, idname)
60 request.cr.execute('SAVEPOINT pagenew')
61 imd = request.registry['ir.model.data']
62 view = request.registry['ir.ui.view']
63 view_model, view_id = imd.get_object_reference(
64 request.cr, request.uid, 'website', 'default_page')
65 newview_id = view.copy(
66 request.cr, request.uid, view_id, context=request.context)
67 newview = view.browse(
68 request.cr, request.uid, newview_id, context=request.context)
70 'arch': newview.arch.replace("website.default_page", xid),
71 'name': "page/%s" % path,
74 # Fuck it, we're doing it live
76 imd.create(request.cr, request.uid, {
79 'model': 'ir.ui.view',
82 }, context=request.context)
83 except psycopg2.IntegrityError:
84 request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
86 request.cr.execute('RELEASE SAVEPOINT pagenew')
87 url = "/page/%s" % path
88 if noredirect is not NOPE:
89 return werkzeug.wrappers.Response(url, mimetype='text/plain')
90 return werkzeug.utils.redirect(url)
92 @website.route('/website/theme_change', type='http', auth="admin")
93 def theme_change(self, theme_id=False, **kwargs):
94 imd = request.registry['ir.model.data']
95 view = request.registry['ir.ui.view']
97 view_model, view_option_id = imd.get_object_reference(
98 request.cr, request.uid, 'website', 'theme')
100 request.cr, request.uid, [('inherit_id', '=', view_option_id)],
101 context=request.context)
102 view.write(request.cr, request.uid, views, {'inherit_id': False},
103 context=request.context)
106 module, xml_id = theme_id.split('.')
107 view_model, view_id = imd.get_object_reference(
108 request.cr, request.uid, module, xml_id)
109 view.write(request.cr, request.uid, [view_id],
110 {'inherit_id': view_option_id}, context=request.context)
112 return request.website.render('website.themes', {'theme_changed': True})
114 @website.route(['/website/snippets'], type='json', auth="public")
116 return request.website.render('website.snippets')
118 @website.route('/page/<path:path>', type='http', auth="public", multilang=True)
119 def page(self, path, **kwargs):
124 html = request.website.render(path, values)
126 html = request.website.render('website.404', values)
129 @website.route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth
130 def customize_template_set(self, view_id):
131 view_obj = request.registry.get("ir.ui.view")
132 view = view_obj.browse(request.cr, request.uid, int(view_id),
133 context=request.context)
137 value = view.inherit_option_id and view.inherit_option_id.id or False
138 view_obj.write(request.cr, request.uid, [view_id], {
140 }, context=request.context)
143 @website.route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth
144 def customize_template_get(self, xml_id, optional=True):
145 imd = request.registry['ir.model.data']
146 view_model, view_theme_id = imd.get_object_reference(
147 request.cr, request.uid, 'website', 'theme')
149 view = request.registry.get("ir.ui.view")
150 views = view._views_get(request.cr, request.uid, xml_id, request.context)
154 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
155 if v.inherit_option_id.id not in done:
157 'name': v.inherit_option_id.name,
162 done[v.inherit_option_id.id] = True
167 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
171 @website.route('/website/get_view_translations', type='json', auth='admin')
172 def get_view_translations(self, xml_id, lang=None):
173 lang = lang or request.context.get('lang')
174 views = self.customize_template_get(xml_id, optional=False)
175 views_ids = [view.get('id') for view in views if view.get('active')]
176 domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
177 irt = request.registry.get('ir.translation')
178 return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
180 @website.route('/website/set_translations', type='json', auth='admin')
181 def set_translations(self, data, lang):
182 irt = request.registry.get('ir.translation')
183 for view_id, trans in data.items():
184 view_id = int(view_id)
186 initial_content = t['initial_content'].strip()
187 new_content = t['new_content'].strip()
188 tid = t['translation_id']
190 old_trans = irt.search_read(
191 request.cr, request.uid,
193 ('type', '=', 'view'),
194 ('res_id', '=', view_id),
196 ('src', '=', initial_content),
199 tid = old_trans[0]['id']
201 vals = {'value': new_content}
202 irt.write(request.cr, request.uid, [tid], vals)
209 'source': initial_content,
210 'value': new_content,
212 irt.create(request.cr, request.uid, new_trans)
213 irt._get_source.clear_cache(irt) # FIXME: find why ir.translation does not invalidate
216 # # FIXME: auth, anybody can upload an attachment if URL known/found
217 @website.route('/website/attach', type='http', auth='admin')
218 def attach(self, func, upload):
219 req = request.httprequest
220 if req.method != 'POST':
221 return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
225 attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
226 'name': upload.filename,
227 'datas': base64.encodestring(upload.read()),
228 'datas_fname': upload.filename,
229 'res_model': 'ir.ui.view',
231 # FIXME: auth=user... no good.
232 url = '/website/attachment/%d' % attachment_id
234 logger.exception("Failed to upload image to attachment")
237 return """<script type='text/javascript'>
238 window.parent['%s'](%s, %s);
239 </script>""" % (func, json.dumps(url), json.dumps(message))
241 @website.route(['/website/publish'], type='json', auth="public")
242 def publish(self, id, object):
244 _object = request.registry[object]
246 obj = _object.browse(request.cr, request.uid, _id)
247 _object.write(request.cr, request.uid, [_id],
248 {'website_published': not obj.website_published},
249 context=request.context)
250 obj = _object.browse(request.cr, request.uid, _id)
251 return obj.website_published and True or False
253 @website.route(['/website/kanban/'], type='http', auth="public")
254 def kanban(self, **post):
255 return request.website.kanban_col(**post)
257 @website.route(['/robots.txt'], type='http', auth="public")
259 return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
261 @website.route(['/sitemap.xml'], type='http', auth="public")
263 return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
265 class Images(http.Controller):
266 @website.route('/website/image', auth="public")
267 def image(self, model, id, field):
268 Model = request.registry[model]
270 response = werkzeug.wrappers.Response()
274 ids = Model.search(request.cr, request.uid,
275 [('id', '=', id)], context=request.context)\
276 or Model.search(request.cr, openerp.SUPERUSER_ID,
277 [('id', '=', id), ('website_published', '=', True)], context=request.context)
280 # file_open may return a StringIO. StringIO can be closed but are
281 # not context managers in Python 2 though that is fixed in 3
282 with contextlib.closing(openerp.tools.misc.file_open(
283 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
285 response.set_data(f.read())
288 concurrency = '__last_update'
289 [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
290 [concurrency, field], context=request.context)
292 if concurrency in record:
293 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
295 response.last_modified = datetime.datetime.strptime(
296 record[concurrency], server_format + '.%f')
298 # just in case we have a timestamp without microseconds
299 response.last_modified = datetime.datetime.strptime(
300 record[concurrency], server_format)
301 # FIXME: no field in record?
302 response.set_etag(hashlib.sha1(record[field]).hexdigest())
303 response.make_conditional(request.httprequest)
305 # conditional request match
306 if response.status_code == 304:
309 return self.set_image_data(response, record[field].decode('base64'))
312 # FIXME: delegate to image?
313 @website.route('/website/attachment/<int:id>', auth='admin')
314 def attachment(self, id):
315 attachment = request.registry['ir.attachment'].browse(
316 request.cr, request.uid, id, request.context)
318 return self.set_image_data(
319 werkzeug.wrappers.Response(),
320 attachment.datas.decode('base64'),
323 def set_image_data(self, response, data, fit=(maxint, maxint)):
324 """ Sets an inferred mime type on the response object, and puts the
325 provided image's data in it, possibly after resizing if requested
327 Returns the response object after setting its mime and content, so
328 the result of ``get_final_image`` can be returned directly.
330 buf = cStringIO.StringIO(data)
332 # FIXME: unknown format or not an image
333 image = Image.open(buf)
334 response.mimetype = PIL_MIME_MAPPING[image.format]
339 if w < max_w and h < max_h:
340 response.set_data(data)
343 image.thumbnail(fit, Image.ANTIALIAS)
344 image.save(response.stream, image.format)
348 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: