[ADD] WebContext object and language selector
[odoo/odoo.git] / addons / website / controllers / main.py
1 # -*- coding: utf-8 -*-
2 import base64
3 import cStringIO
4 import hashlib
5 import json
6 import logging
7 import os
8
9 import psycopg2
10 import werkzeug
11 import werkzeug.exceptions
12 import werkzeug.utils
13 import werkzeug.wrappers
14 from PIL import Image
15
16 import openerp
17 from openerp.addons.website.website import route
18 from openerp.addons.web import http
19 from openerp.addons.web.http import request
20
21 logger = logging.getLogger(__name__)
22
23
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
28     else:
29         request.uid = request.session.uid
30 http.auth_methods['public'] = auth_method_public
31
32 NOPE = object()
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     @route('/', type='http', auth="admin")
39     def index(self, **kw):
40         return self.page("website.homepage")
41
42     @http.route('/admin', type='http', auth="none")
43     def admin(self, *args, **kw):
44         return super(Website, self).index(*args, **kw)
45
46     @route('/pagenew/<path:path>', type='http', auth="admin")
47     def pagenew(self, path, noredirect=NOPE):
48         if '.' in path:
49             module, idname = path.split('.', 1)
50         else:
51             module = 'website'
52             idname = path
53         path = "%s.%s" % (module, idname)
54
55         request.cr.execute('SAVEPOINT pagenew')
56         imd = request.registry['ir.model.data']
57         view = request.registry['ir.ui.view']
58         view_model, view_id = imd.get_object_reference(request.cr, request.uid, 'website', 'default_page')
59         newview_id = view.copy(request.cr, request.uid, view_id)
60         newview = view.browse(request.cr, request.uid, newview_id)
61         newview.write({
62             'arch': newview.arch.replace("website.default_page", path),
63             'name': "page/%s" % path,
64             'page': True,
65         })
66         # Fuck it, we're doing it live
67         try:
68             imd.create(request.cr, request.uid, {
69                 'name': idname,
70                 'module': module,
71                 'model': 'ir.ui.view',
72                 'res_id': newview_id,
73                 'noupdate': True
74             })
75         except psycopg2.IntegrityError:
76             request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
77         else:
78             request.cr.execute('RELEASE SAVEPOINT pagenew')
79         url = "/page/%s" % path
80         if noredirect is not NOPE:
81             return werkzeug.wrappers.Response(url, mimetype='text/plain')
82         return werkzeug.utils.redirect(url)
83
84     @route('/website/theme_change', type='http', auth="admin")
85     def theme_change(self, theme_id=False, **kwargs):
86         imd = request.registry['ir.model.data']
87         view = request.registry['ir.ui.view']
88
89         view_model, view_option_id = imd.get_object_reference(request.cr, request.uid, 'website', 'theme')
90         views = view.search(request.cr, request.uid, [('inherit_id','=',view_option_id)])
91         view.write(request.cr, request.uid, views, {'inherit_id': False})
92
93         if theme_id:
94             module, xml_id = theme_id.split('.')
95             view_model, view_id = imd.get_object_reference(request.cr, request.uid, module, xml_id)
96             view.write(request.cr, request.uid, [view_id], {'inherit_id':view_option_id})
97
98         request.webcontext['theme_changed'] = True
99         return request.webcontext.render('website.themes')
100
101     @route('/page/<path:path>', type='http', auth="admin")
102     def page(self, path, **kwargs):
103         request.webcontext['path'] = path
104         try:
105             html = request.webcontext.render(path)
106         except ValueError:
107             html = request.webcontext.render('website.404')
108         return html
109
110     @route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth
111     def customize_template_set(self, view_id):
112         view_obj = request.registry.get("ir.ui.view")
113         view = view_obj.browse(request.cr, request.uid, int(view_id), context=request.context)
114         if view.inherit_id:
115             value = False
116         else:
117             value = view.inherit_option_id and view.inherit_option_id.id or False
118         view_obj.write(request.cr, request.uid, [view_id], {
119             'inherit_id': value
120         }, context=request.context)
121         return True
122
123     @route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth
124     def customize_template_get(self, xml_id):
125         imd = request.registry['ir.model.data']
126         view_model, view_theme_id = imd.get_object_reference(request.cr, request.uid, 'website', 'theme')
127
128         view = request.registry.get("ir.ui.view")
129         views = view._views_get(request.cr, request.uid, xml_id, request.context)
130         done = {}
131         result = []
132         for v in views:
133             if v.inherit_option_id and v.inherit_option_id.id<>view_theme_id:
134                 if v.inherit_option_id.id not in done:
135                     result.append({
136                         'name': v.inherit_option_id.name,
137                         'header': True,
138                         'active': False
139                     })
140                     done[v.inherit_option_id.id] = True
141                 result.append({
142                     'name': v.name,
143                     'id': v.id,
144                     'header': False,
145                     'active': v.inherit_id.id == v.inherit_option_id.id
146                 })
147         return result
148
149     @route('/website/attach', type='http', auth='admin') # FIXME: auth
150     def attach(self, func, upload):
151         req = request.httprequest
152         if req.method != 'POST':
153             return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
154
155         url = message = None
156         try:
157             attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
158                 'name': upload.filename,
159                 'datas': base64.encodestring(upload.read()),
160                 'datas_fname': upload.filename,
161                 'res_model': 'ir.ui.view',
162             }, request.context)
163             # FIXME: auth=user... no good.
164             url = '/website/attachment/%d' % attachment_id
165         except Exception, e:
166             logger.exception("Failed to upload image to attachment")
167             message = str(e)
168
169         return """<script type='text/javascript'>
170             window.parent['%s'](%s, %s);
171         </script>""" % (func, json.dumps(url), json.dumps(message))
172
173     @route('/website/attachment/<int:id>', type='http', auth="admin")
174     def attachment(self, id):
175         # FIXME: can't use Binary.image because auth=user and website attachments need to be public
176         attachment = request.registry['ir.attachment'].browse(
177             request.cr, request.uid, id, request.context)
178
179         buf = cStringIO.StringIO(base64.decodestring(attachment.datas))
180
181         image = Image.open(buf)
182         mime = PIL_MIME_MAPPING[image.format]
183
184         w, h = image.size
185         resized = w > MAX_IMAGE_WIDTH or h > MAX_IMAGE_HEIGHT
186
187         # If saving unnecessary, just send the image buffer, don't go through
188         # Image.save() (especially as it breaks animated gifs)
189         if not resized:
190             buf.seek(0)
191             return werkzeug.wrappers.Response(buf, status=200, mimetype=mime)
192
193         image.thumbnail(IMAGE_LIMITS, Image.ANTIALIAS)
194         response = werkzeug.wrappers.Response(status=200, mimetype=mime)
195         image.save(response.stream, image.format)
196         return response
197
198     @route('/website/image', type='http', auth="public")
199     def image(self, model, id, field, **kw):
200         last_update = '__last_update'
201         Model = request.registry[model]
202         headers = [('Content-Type', 'image/png')]
203         etag = request.httprequest.headers.get('If-None-Match')
204         hashed_session = hashlib.md5(request.session_id).hexdigest()
205         retag = hashed_session
206         try:
207             if etag:
208                 date = Model.read(request.cr, request.uid, [id], [last_update], request.context)[0].get(last_update)
209                 if hashlib.md5(date).hexdigest() == etag:
210                     return werkzeug.wrappers.Response(status=304)
211
212             res = Model.read(request.cr, request.uid, [id], [last_update, field], request.context)[0]
213             retag = hashlib.md5(res.get(last_update)).hexdigest()
214             image_base64 = res.get(field)
215
216             if kw.get('resize'):
217                 resize = kw.get('resize').split(',')
218                 if len(resize) == 2 and int(resize[0]) and int(resize[1]):
219                     width = int(resize[0])
220                     height = int(resize[1])
221                     # resize maximum 500*500
222                     if width > 500: width = 500
223                     if height > 500: height = 500
224                     image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
225
226             image_data = base64.b64decode(image_base64)
227         except Exception:
228             image_data = open(os.path.join(http.addons_manifest['web']['addons_path'], 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
229
230         headers.append(('ETag', retag))
231         headers.append(('Content-Length', len(image_data)))
232         try:
233             ncache = int(kw.get('cache'))
234             headers.append(('Cache-Control', 'no-cache' if ncache == 0 else 'max-age=%s' % (ncache)))
235         except:
236             pass
237         return request.make_response(image_data, headers)
238
239     @route(['/website/publish/'], type='http', auth="public")
240     def publish(self, **post):
241         _id = int(post['id'])
242         _object = request.registry[post['object']]
243
244         obj = _object.browse(request.cr, request.uid, _id)
245         _object.write(request.cr, request.uid, [_id], {'website_published': not obj.website_published})
246         obj = _object.browse(request.cr, request.uid, _id)
247
248         return obj.website_published and "1" or "0"
249
250     @route(['/website/kanban/'], type='http', auth="public")
251     def kanban(self, **post):
252         return request.registry['website'].kanban_col(**post)
253
254 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: