diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index bd3f7285126..2d977287560 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -27,6 +27,10 @@ def auth_method_public(): http.auth_methods['public'] = auth_method_public +# PIL images have a type flag, but no MIME. Reverse type flag to MIME. +PIL_MIME_MAPPING = {'PNG': 'image/png', 'JPEG': 'image/jpeg', 'GIF': 'image/gif', } +# Completely arbitrary limits +MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768) class Website(openerp.addons.web.controllers.main.Home): @http.route('/', type='http', auth="admin") def index(self, **kw): @@ -45,7 +49,8 @@ class Website(openerp.addons.web.controllers.main.Home): newview = view.browse(request.cr, request.uid, newview_id, context={}) newview.write({ 'arch': newview.arch.replace("website.default_page", path), - 'name': "page/%s" % path + 'name': "page/%s" % path, + 'page': True, }) if '.' in path: module, idname = path.split('.') @@ -145,15 +150,20 @@ class Website(openerp.addons.web.controllers.main.Home): buf = cStringIO.StringIO(base64.decodestring(attachment.datas)) image = Image.open(buf) - image.thumbnail((1024, 768), Image.ANTIALIAS) + mime = PIL_MIME_MAPPING[image.format] - response = werkzeug.wrappers.Response(status=200, mimetype={ - 'PNG': 'image/png', - 'JPEG': 'image/jpeg', - 'GIF': 'image/gif', - }[image.format]) + w, h = image.size + resized = w > MAX_IMAGE_WIDTH or h > MAX_IMAGE_HEIGHT + + # If saving unnecessary, just send the image buffer, don't go through + # Image.save() (especially as it breaks animated gifs) + if not resized: + buf.seek(0) + return werkzeug.wrappers.Response(buf, status=200, mimetype=mime) + + image.thumbnail(IMAGE_LIMITS, Image.ANTIALIAS) + response = werkzeug.wrappers.Response(status=200, mimetype=mime) image.save(response.stream, image.format) - return response @http.route('/website/image', type='http', auth="public") diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index c3d8dd1e5f3..f245d98de6a 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -250,7 +250,7 @@ ]},{ name: 'span', items: [ "Link", "Unlink", "Blockquote", "BulletedList", - "NumberedList", "Indent", "Outdent", + "NumberedList", "Indent", "Outdent" ]},{ name: 'justify', items: [ "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock" @@ -439,6 +439,146 @@ }, }); + function noop() {} + var alter_dialog = { + image: function (definition) { + definition.removeContents('Link'); + definition.removeContents('advanced'); + + var upload = definition.getContents('Upload'); + upload.add({ + type: 'select', + label: 'Existing attachments', + id: 'ir_attachment', + items: [['']], + /** + * On dialog load, fetch all attachments (on ir.ui.view => + * previously uploaded images) and add them to the select's + * options (items array & add method) + */ + onLoad: function () { + var field = this; + + // FIXME: fuck this garbage, also fuck openerp.Model + return openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'ir.attachment', + method: 'search_read', + args: [], + kwargs: { + fields: ['name'], + domain: [['res_model', '=', 'ir.ui.view']], + order: 'name', + } + }).then(function (results) { + _(results).each(function (result) { + field.add(result.name, result.id); + }); + }); + }, + /** + * The image widgets uses "txtUrl" to do most of its stuff. + * Synchronize select & txtUrl by generating the correct URL + * and setting it there + */ + onChange: function () { + var id = this.getValue(); + var url = this.getDialog().getContentElement('info', 'txtUrl'); + if (!id) { + url.setValue(''); + return; + } + + url.setValue('/website/attachment/' + id); + }, + }); + // Override uploadButton to send its information to the select + // created above instead of directly to txtUrl. The select will + // propagate to txtUrl + upload.get('uploadButton').filebrowser = { + onSelect: function (url) { + var id = url.split('/').pop(); + var attachments = this.getDialog().getContentElement('Upload', 'ir_attachment'); + // TODO: return supplementary info to get image/attachment name? + attachments.add(id, id); + attachments.setValue(id); + } + }; + + var old_show = definition.onShow; + definition.onShow = function () { + // CKEDITOR does not *override* onShow, is smashes the existing + // one instead, so override "by hand" + if (old_show) { + old_show.call(this); + } + // Assloads of code in the image plugin just go and tear into + // the info tab without a care, so can't just remove the tab or + // its content. Hide the tab instead, the effect is roughly the + // same. + this.hidePage('info'); + // Force the dialog to always and only display the Upload tab + this.selectPage('Upload'); + this.on('selectPage', function (e) { + setTimeout(function () { + if (e.data.page !== 'Upload') { + this.selectPage('Upload'); + } + }.bind(this), 0); + }); + } + }, + link: function (definition) { + definition.removeContents('target'); + definition.removeContents('advanced'); + + var info = definition.getContents('info'); + info.remove('linkType'); + info.remove('anchorOptions'); + info.remove('emailOptions'); + + info.get('urlOptions').children[0].widths = [ '0%', '100%' ]; + info.get('protocol').style = 'display: none'; + // TODO: sync edition of url to website_pages? + info.add({ + type: 'select', + label: "Existing page", + id: 'website_pages', + items: [['']], + /** + * onload fetch all the pages existing in the website, then + * display that. + */ + onLoad: function () { + var field = this; + + return openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'website', + method: 'list_pages', + args: [], + kwargs: {} + }).then(function (results) { + _(results).each(function (result) { + field.add(result.name, result.url); + }); + }); + }, + onChange: function () { + var url = this.getValue(); + var url_field = this.getDialog().getContentElement('info', 'url'); + if (!url) { + url_field.setValue(''); + return; + } + + url_field.setValue(url); + } + }) + } + }; + CKEDITOR.on('dialogDefinition', function (ev) { + (alter_dialog[ev.data.name] || noop)(ev.data.definition); + }); + var all_ready = null; var dom_ready = $.Deferred(); $(dom_ready.resolve); diff --git a/addons/website/views/views.xml b/addons/website/views/views.xml index bc6c73820c9..d3bda075b37 100644 --- a/addons/website/views/views.xml +++ b/addons/website/views/views.xml @@ -132,7 +132,7 @@ -