[MERGE] from trunk
[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 import website
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     @website.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      # 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):
49         if '.' in path:
50             module, idname = path.split('.', 1)
51         else:
52             module = 'website'
53             idname = path
54         xid = "%s.%s" % (module, idname)
55
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)
65         newview.write({
66             'arch': newview.arch.replace("website.default_page", xid),
67             'name': "page/%s" % path,
68             'page': True,
69         })
70         # Fuck it, we're doing it live
71         try:
72             imd.create(request.cr, request.uid, {
73                 'name': idname,
74                 'module': module,
75                 'model': 'ir.ui.view',
76                 'res_id': newview_id,
77                 'noupdate': True
78             }, context=request.context)
79         except psycopg2.IntegrityError:
80             request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
81         else:
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)
87
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']
92
93         view_model, view_option_id = imd.get_object_reference(
94             request.cr, request.uid, 'website', 'theme')
95         views = view.search(
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)
100
101         if theme_id:
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)
107
108         return request.website.render('website.themes', {'theme_changed': True})
109
110     @website.route('/page/<path:path>', type='http', auth="admin")
111     def page(self, path, **kwargs):
112         values = {
113             'path': path,
114         }
115         try:
116             html = request.website.render(path, values)
117         except ValueError:
118             html = request.website.render('website.404', values)
119         return html
120
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)
126         if view.inherit_id:
127             value = False
128         else:
129             value = view.inherit_option_id and view.inherit_option_id.id or False
130         view_obj.write(request.cr, request.uid, [view_id], {
131             'inherit_id': value
132         }, context=request.context)
133         return True
134
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')
140
141         view = request.registry.get("ir.ui.view")
142         views = view._views_get(request.cr, request.uid, xml_id, request.context)
143         done = {}
144         result = []
145         for v in views:
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:
148                     result.append({
149                         'name': v.inherit_option_id.name,
150                         'id': v.id,
151                         'header': True,
152                         'active': False
153                     })
154                     done[v.inherit_option_id.id] = True
155                 result.append({
156                     'name': v.name,
157                     'id': v.id,
158                     'header': False,
159                     'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
160                 })
161         return result
162
163     #  # FIXME: auth, anybody can upload an attachment if URL known/found
164     @website.route('/website/attach', type='http', auth='admin')
165     def attach(self, func, upload):
166         req = request.httprequest
167         if req.method != 'POST':
168             return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
169
170         url = message = None
171         try:
172             attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
173                 'name': upload.filename,
174                 'datas': base64.encodestring(upload.read()),
175                 'datas_fname': upload.filename,
176                 'res_model': 'ir.ui.view',
177             }, request.context)
178             # FIXME: auth=user... no good.
179             url = '/website/attachment/%d' % attachment_id
180         except Exception, e:
181             logger.exception("Failed to upload image to attachment")
182             message = str(e)
183
184         return """<script type='text/javascript'>
185             window.parent['%s'](%s, %s);
186         </script>""" % (func, json.dumps(url), json.dumps(message))
187
188     @website.route('/website/attachment/<int:id>', type='http', auth="admin")
189     def attachment(self, id):
190         # TODO: provide actual thumbnails?
191         # FIXME: can't use Binary.image because auth=user and website attachments need to be public
192         attachment = request.registry['ir.attachment'].browse(
193             request.cr, request.uid, id, request.context)
194
195         buf = cStringIO.StringIO(base64.decodestring(attachment.datas))
196
197         image = Image.open(buf)
198         mime = PIL_MIME_MAPPING[image.format]
199
200         w, h = image.size
201         resized = w > MAX_IMAGE_WIDTH or h > MAX_IMAGE_HEIGHT
202
203         # If saving unnecessary, just send the image buffer, don't go through
204         # Image.save() (especially as it breaks animated gifs)
205         if not resized:
206             buf.seek(0)
207             return werkzeug.wrappers.Response(buf, status=200, mimetype=mime)
208
209         image.thumbnail(IMAGE_LIMITS, Image.ANTIALIAS)
210         response = werkzeug.wrappers.Response(status=200, mimetype=mime)
211         image.save(response.stream, image.format)
212         return response
213
214     @website.route('/website/image', type='http', auth="public")
215     def image(self, model, id, field, **kw):
216         last_update = '__last_update'
217         Model = request.registry[model]
218         headers = [('Content-Type', 'image/png')]
219         etag = request.httprequest.headers.get('If-None-Match')
220         hashed_session = hashlib.md5(request.session_id).hexdigest()
221         retag = hashed_session
222         try:
223             if etag:
224                 date = Model.read(request.cr, request.uid, [id], [last_update], request.context)[0].get(last_update)
225                 if hashlib.md5(date).hexdigest() == etag:
226                     return werkzeug.wrappers.Response(status=304)
227
228             res = Model.read(request.cr, request.uid, [id], [last_update, field], request.context)[0]
229             retag = hashlib.md5(res.get(last_update)).hexdigest()
230             image_base64 = res.get(field)
231
232             if kw.get('resize'):
233                 resize = kw.get('resize').split(',')
234                 if len(resize) == 2 and int(resize[0]) and int(resize[1]):
235                     width = int(resize[0])
236                     height = int(resize[1])
237                     # resize maximum 500*500
238                     if width > 500:
239                         width = 500
240                     if height > 500:
241                         height = 500
242                     image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
243
244             image_data = base64.b64decode(image_base64)
245         except Exception:
246             image_data = open(os.path.join(http.addons_manifest['web']['addons_path'], 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
247
248         headers.append(('ETag', retag))
249         headers.append(('Content-Length', len(image_data)))
250         try:
251             ncache = int(kw.get('cache'))
252             headers.append(('Cache-Control', 'no-cache' if ncache == 0 else 'max-age=%s' % (ncache)))
253         except:
254             pass
255         return request.make_response(image_data, headers)
256
257     @website.route(['/website/publish/'], type='http', auth="public")
258     def publish(self, **post):
259         _id = int(post['id'])
260         _object = request.registry[post['object']]
261
262         obj = _object.browse(request.cr, request.uid, _id)
263         _object.write(request.cr, request.uid, [_id],
264                       {'website_published': not obj.website_published},
265                       context=request.context)
266         obj = _object.browse(request.cr, request.uid, _id)
267
268         return obj.website_published and "1" or "0"
269
270     @website.route(['/website/kanban/'], type='http', auth="public")
271     def kanban(self, **post):
272         return request.website.kanban_col(**post)
273
274 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: