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