1 # -*- coding: utf-8 -*-
11 import werkzeug.exceptions
13 import werkzeug.wrappers
17 from openerp.addons.website import website
18 from openerp.addons.web import http
19 from openerp.addons.web.http import request
21 logger = logging.getLogger(__name__)
24 def auth_method_public():
25 registry = openerp.modules.registry.RegistryManager.get(request.db)
26 if not request.session.uid:
27 request.uid = registry['website'].get_public_user().id
29 request.uid = request.session.uid
30 http.auth_methods['public'] = auth_method_public
33 # PIL images have a type flag, but no MIME. Reverse type flag to MIME.
34 PIL_MIME_MAPPING = {'PNG': 'image/png', 'JPEG': 'image/jpeg', 'GIF': 'image/gif', }
35 # Completely arbitrary limits
36 MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
37 class Website(openerp.addons.web.controllers.main.Home):
38 @website.route('/', type='http', auth="admin")
39 def index(self, **kw):
40 return self.page("website.homepage")
42 @http.route('/admin', type='http', auth="none")
43 def admin(self, *args, **kw):
44 return super(Website, self).index(*args, **kw)
46 # FIXME: auth, if /pagenew known anybody can create new empty page
47 @website.route('/pagenew/<path:path>', type='http', auth="admin")
48 def pagenew(self, path, noredirect=NOPE):
50 module, idname = path.split('.', 1)
54 path = "%s.%s" % (module, idname)
56 request.cr.execute('SAVEPOINT pagenew')
57 imd = request.registry['ir.model.data']
58 view = request.registry['ir.ui.view']
59 view_model, view_id = imd.get_object_reference(
60 request.cr, request.uid, 'website', 'default_page')
61 newview_id = view.copy(
62 request.cr, request.uid, view_id, context=request.context)
63 newview = view.browse(
64 request.cr, request.uid, newview_id, context=request.context)
66 'arch': newview.arch.replace("website.default_page", path),
67 'name': "page/%s" % path,
70 # Fuck it, we're doing it live
72 imd.create(request.cr, request.uid, {
75 'model': 'ir.ui.view',
78 }, context=request.context)
79 except psycopg2.IntegrityError:
80 request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
82 request.cr.execute('RELEASE SAVEPOINT pagenew')
83 url = "/page/%s" % path
84 if noredirect is not NOPE:
85 return werkzeug.wrappers.Response(url, mimetype='text/plain')
86 return werkzeug.utils.redirect(url)
88 @website.route('/website/theme_change', type='http', auth="admin")
89 def theme_change(self, theme_id=False, **kwargs):
90 imd = request.registry['ir.model.data']
91 view = request.registry['ir.ui.view']
93 view_model, view_option_id = imd.get_object_reference(
94 request.cr, request.uid, 'website', 'theme')
96 request.cr, request.uid, [('inherit_id', '=', view_option_id)],
97 context=request.context)
98 view.write(request.cr, request.uid, views, {'inherit_id': False},
99 context=request.context)
102 module, xml_id = theme_id.split('.')
103 view_model, view_id = imd.get_object_reference(
104 request.cr, request.uid, module, xml_id)
105 view.write(request.cr, request.uid, [view_id],
106 {'inherit_id': view_option_id}, context=request.context)
108 return request.website.render('website.themes', {'theme_changed': True})
110 @website.route('/page/<path:path>', type='http', auth="admin")
111 def page(self, path, **kwargs):
116 html = request.website.render(path, values)
118 html = request.website.render('website.404', values)
121 @website.route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth
122 def customize_template_set(self, view_id):
123 view_obj = request.registry.get("ir.ui.view")
124 view = view_obj.browse(request.cr, request.uid, int(view_id),
125 context=request.context)
129 value = view.inherit_option_id and view.inherit_option_id.id or False
130 view_obj.write(request.cr, request.uid, [view_id], {
132 }, context=request.context)
135 @website.route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth
136 def customize_template_get(self, xml_id, optional=True):
137 imd = request.registry['ir.model.data']
138 view_model, view_theme_id = imd.get_object_reference(
139 request.cr, request.uid, 'website', 'theme')
141 view = request.registry.get("ir.ui.view")
142 views = view._views_get(request.cr, request.uid, xml_id, request.context)
146 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
147 if v.inherit_option_id.id not in done:
149 'name': v.inherit_option_id.name,
154 done[v.inherit_option_id.id] = True
159 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
163 @website.route('/website/get_view_translations', type='json', auth='admin')
164 def get_view_translations(self, xml_id, optional=False):
165 view = request.registry.get("ir.ui.view")
166 views = view._views_get(request.cr, request.uid, xml_id, request.context)
169 @website.route('/website/set_translations', type='json', auth='admin')
170 def set_translations(self, data, lang):
171 irt = request.registry.get('ir.translation')
172 for view_id, trans in data.items():
173 view_id = int(view_id)
175 initial_content = t['initial_content'].strip()
176 new_content = t['new_content'].strip()
177 old_trans = irt.search_read(
178 request.cr, request.uid,
180 ('type', '=', 'view'),
181 ('res_id', '=', view_id),
183 ('src', '=', initial_content),
186 vals = {'value': new_content}
187 irt.write(request.cr, request.uid, [old_trans[0]['id']], vals)
194 'source': initial_content,
195 'value': new_content,
197 irt.create(request.cr, request.uid, new_trans)
198 irt._get_source.clear_cache(irt) # FIXME: find why ir.translation does not invalidate
201 # # FIXME: auth, anybody can upload an attachment if URL known/found
202 @website.route('/website/attach', type='http', auth='admin')
203 def attach(self, func, upload):
204 req = request.httprequest
205 if req.method != 'POST':
206 return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
210 attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
211 'name': upload.filename,
212 'datas': base64.encodestring(upload.read()),
213 'datas_fname': upload.filename,
214 'res_model': 'ir.ui.view',
216 # FIXME: auth=user... no good.
217 url = '/website/attachment/%d' % attachment_id
219 logger.exception("Failed to upload image to attachment")
222 return """<script type='text/javascript'>
223 window.parent['%s'](%s, %s);
224 </script>""" % (func, json.dumps(url), json.dumps(message))
226 @website.route('/website/attachment/<int:id>', type='http', auth="admin")
227 def attachment(self, id):
228 # TODO: provide actual thumbnails?
229 # FIXME: can't use Binary.image because auth=user and website attachments need to be public
230 attachment = request.registry['ir.attachment'].browse(
231 request.cr, request.uid, id, request.context)
233 buf = cStringIO.StringIO(base64.decodestring(attachment.datas))
235 image = Image.open(buf)
236 mime = PIL_MIME_MAPPING[image.format]
239 resized = w > MAX_IMAGE_WIDTH or h > MAX_IMAGE_HEIGHT
241 # If saving unnecessary, just send the image buffer, don't go through
242 # Image.save() (especially as it breaks animated gifs)
245 return werkzeug.wrappers.Response(buf, status=200, mimetype=mime)
247 image.thumbnail(IMAGE_LIMITS, Image.ANTIALIAS)
248 response = werkzeug.wrappers.Response(status=200, mimetype=mime)
249 image.save(response.stream, image.format)
252 @website.route('/website/image', type='http', auth="public")
253 def image(self, model, id, field, **kw):
254 last_update = '__last_update'
255 Model = request.registry[model]
256 headers = [('Content-Type', 'image/png')]
257 etag = request.httprequest.headers.get('If-None-Match')
258 hashed_session = hashlib.md5(request.session_id).hexdigest()
259 retag = hashed_session
262 date = Model.read(request.cr, request.uid, [id], [last_update], request.context)[0].get(last_update)
263 if hashlib.md5(date).hexdigest() == etag:
264 return werkzeug.wrappers.Response(status=304)
266 res = Model.read(request.cr, request.uid, [id], [last_update, field], request.context)[0]
267 retag = hashlib.md5(res.get(last_update)).hexdigest()
268 image_base64 = res.get(field)
271 resize = kw.get('resize').split(',')
272 if len(resize) == 2 and int(resize[0]) and int(resize[1]):
273 width = int(resize[0])
274 height = int(resize[1])
275 # resize maximum 500*500
280 image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
282 image_data = base64.b64decode(image_base64)
284 image_data = open(os.path.join(http.addons_manifest['web']['addons_path'], 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
286 headers.append(('ETag', retag))
287 headers.append(('Content-Length', len(image_data)))
289 ncache = int(kw.get('cache'))
290 headers.append(('Cache-Control', 'no-cache' if ncache == 0 else 'max-age=%s' % (ncache)))
293 return request.make_response(image_data, headers)
295 @website.route(['/website/publish/'], type='http', auth="public")
296 def publish(self, **post):
297 _id = int(post['id'])
298 _object = request.registry[post['object']]
300 obj = _object.browse(request.cr, request.uid, _id)
301 _object.write(request.cr, request.uid, [_id],
302 {'website_published': not obj.website_published},
303 context=request.context)
304 obj = _object.browse(request.cr, request.uid, _id)
306 return obj.website_published and "1" or "0"
308 @website.route(['/website/kanban/'], type='http', auth="public")
309 def kanban(self, **post):
310 return request.website.kanban_col(**post)
312 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: