diff --git a/addons/product/static/img/product_product_11-image.jpg b/addons/product/static/img/product_product_11-image.jpg deleted file mode 100644 index 0c1907372cf..00000000000 Binary files a/addons/product/static/img/product_product_11-image.jpg and /dev/null differ diff --git a/addons/product/static/img/product_product_11-image.png b/addons/product/static/img/product_product_11-image.png index 7508b319bc6..974cea9521d 100644 Binary files a/addons/product/static/img/product_product_11-image.png and b/addons/product/static/img/product_product_11-image.png differ diff --git a/addons/product/static/img/product_product_4-image.jpg b/addons/product/static/img/product_product_4-image.jpg deleted file mode 100644 index 61e5c5b9ce8..00000000000 Binary files a/addons/product/static/img/product_product_4-image.jpg and /dev/null differ diff --git a/addons/product/static/img/product_product_4-image.png b/addons/product/static/img/product_product_4-image.png index 5dc4a52bfd0..725b3dca9ec 100644 Binary files a/addons/product/static/img/product_product_4-image.png and b/addons/product/static/img/product_product_4-image.png differ diff --git a/addons/product/static/img/product_product_5-image.png b/addons/product/static/img/product_product_5-image.png index 1349f6991a4..55d380c1857 100644 Binary files a/addons/product/static/img/product_product_5-image.png and b/addons/product/static/img/product_product_5-image.png differ diff --git a/addons/product/static/img/product_product_6-image.jpg b/addons/product/static/img/product_product_6-image.jpg deleted file mode 100644 index d391476db83..00000000000 Binary files a/addons/product/static/img/product_product_6-image.jpg and /dev/null differ diff --git a/addons/product/static/img/product_product_6-image.png b/addons/product/static/img/product_product_6-image.png index e0c97f19e2a..5ad71a54ee9 100644 Binary files a/addons/product/static/img/product_product_6-image.png and b/addons/product/static/img/product_product_6-image.png differ diff --git a/addons/product/static/img/product_product_7-image.png b/addons/product/static/img/product_product_7-image.png index 7de5d27c791..e18c80ddbd3 100644 Binary files a/addons/product/static/img/product_product_7-image.png and b/addons/product/static/img/product_product_7-image.png differ diff --git a/addons/product/static/img/product_product_8-image.jpg b/addons/product/static/img/product_product_8-image.jpg deleted file mode 100644 index 2d9874aa7fe..00000000000 Binary files a/addons/product/static/img/product_product_8-image.jpg and /dev/null differ diff --git a/addons/product/static/img/product_product_8-image.png b/addons/product/static/img/product_product_8-image.png index 613465f6490..5ac00bfb697 100644 Binary files a/addons/product/static/img/product_product_8-image.png and b/addons/product/static/img/product_product_8-image.png differ diff --git a/addons/product/static/img/product_product_9-image.jpg b/addons/product/static/img/product_product_9-image.jpg deleted file mode 100644 index cdc12c87203..00000000000 Binary files a/addons/product/static/img/product_product_9-image.jpg and /dev/null differ diff --git a/addons/product/static/img/product_product_9-image.png b/addons/product/static/img/product_product_9-image.png index 724d1085cb9..c8a5cf65f3c 100644 Binary files a/addons/product/static/img/product_product_9-image.png and b/addons/product/static/img/product_product_9-image.png differ diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 2ab0095365f..908f56c11a0 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -32,6 +32,7 @@ except ImportError: import openerp import openerp.modules.registry from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound +from openerp.modules import get_module_resource from openerp.tools import topological_sort from openerp.tools.translate import _ from openerp import http @@ -1180,7 +1181,8 @@ class Binary(http.Controller): '/logo.png', ], type='http', auth="none") def company_logo(self, dbname=None, **kw): - # TODO add etag, refactor to use /image code for etag + imgname = 'logo.png' + placeholder = functools.partial(get_module_resource, 'web', 'static', 'src', 'img') uid = None if request.session.db: dbname = request.session.db @@ -1192,13 +1194,13 @@ class Binary(http.Controller): uid = openerp.SUPERUSER_ID if not dbname: - image_data = self.placeholder('logo.png') + response = http.send_file(placeholder(imgname)) else: try: # create an empty registry registry = openerp.modules.registry.Registry(dbname) with registry.cursor() as cr: - cr.execute("""SELECT c.logo_web + cr.execute("""SELECT c.logo_web, c.write_date FROM res_users u LEFT JOIN res_company c ON c.id = u.company_id @@ -1206,17 +1208,14 @@ class Binary(http.Controller): """, (uid,)) row = cr.fetchone() if row and row[0]: - image_data = str(row[0]).decode('base64') + image_data = StringIO(str(row[0]).decode('base64')) + response = http.send_file(image_data, filename=imgname, mtime=row[1]) else: - image_data = self.placeholder('nologo.png') + response = http.send_file(placeholder('nologo.png')) except Exception: - image_data = self.placeholder('logo.png') + response = http.send_file(placeholder(imgname)) - headers = [ - ('Content-Type', 'image/png'), - ('Content-Length', len(image_data)), - ] - return request.make_response(image_data, headers) + return response class Action(http.Controller): diff --git a/addons/web/static/src/img/logo.png b/addons/web/static/src/img/logo.png index a0a79df29fb..eeb685b93c2 100644 Binary files a/addons/web/static/src/img/logo.png and b/addons/web/static/src/img/logo.png differ diff --git a/addons/web/static/src/img/logo2.png b/addons/web/static/src/img/logo2.png index 1651b0c94a8..dc9ab9a3c9d 100644 Binary files a/addons/web/static/src/img/logo2.png and b/addons/web/static/src/img/logo2.png differ diff --git a/addons/web/static/src/img/nologo.png b/addons/web/static/src/img/nologo.png index be3cd13a94e..9f8cf01d60f 100644 Binary files a/addons/web/static/src/img/nologo.png and b/addons/web/static/src/img/nologo.png differ diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index d5d9b7582e5..4fc90e4a8bc 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -8,8 +8,6 @@ import xml.etree.ElementTree as ET import logging import re -from sys import maxint - import werkzeug.utils import urllib2 import werkzeug.wrappers @@ -17,7 +15,8 @@ from PIL import Image import openerp from openerp.addons.web import http -from openerp.http import request, Response +from openerp.http import request, STATIC_CACHE +from openerp.tools import image_save_for_web logger = logging.getLogger(__name__) @@ -316,7 +315,7 @@ class Website(openerp.addons.web.controllers.main.Home): return True @http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True) - def attach(self, func, upload=None, url=None): + def attach(self, func, upload=None, url=None, disable_optimization=None): Attachments = request.registry['ir.attachment'] website_url = message = None @@ -339,6 +338,9 @@ class Website(openerp.addons.web.controllers.main.Home): u"Image size excessive, uploaded images must be smaller " u"than 42 million pixel") + if not disable_optimization and image.format in ('PNG', 'JPEG'): + image_data = image_save_for_web(image) + attachment_id = Attachments.create(request.cr, request.uid, { 'name': upload.filename, 'datas': image_data.encode('base64'), @@ -397,8 +399,8 @@ class Website(openerp.addons.web.controllers.main.Home): @http.route([ '/website/image', - '/website/image///', - '/website/image////x' + '/website/image/--', + '/website/image/---x' ], auth="public", website=True) def website_image(self, model, id, field, max_width=None, max_height=None): """ Fetches the requested field and ensures it does not go above @@ -416,9 +418,12 @@ class Website(openerp.addons.web.controllers.main.Home): all cases. """ try: + idsha = id.split('_') + id = idsha[0] response = werkzeug.wrappers.Response() return request.registry['website']._image( - request.cr, request.uid, model, id, field, response, max_width, max_height) + request.cr, request.uid, model, id, field, response, max_width, max_height, + cache=STATIC_CACHE if len(idsha) > 1 else None) except Exception: logger.exception("Cannot render image field %r of record %s[%s] at size(%s,%s)", field, model, id, max_width, max_height) diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index 9d528c9329b..a4d94cbefe0 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -286,21 +286,17 @@ class Image(orm.AbstractModel): def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None): if options is None: options = {} - classes = ['img', 'img-responsive'] + options.get('class', '').split() + aclasses = ['img', 'img-responsive'] + options.get('class', '').split() + classes = ' '.join(itertools.imap(escape, aclasses)) - url_frags = { - 'classes': ' '.join(itertools.imap(escape, classes)), - 'model': record._model._name, - 'id': record.id, - 'field': field_name, - 'max_size': '', - } + max_size = None max_width, max_height = options.get('max_width', 0), options.get('max_height', 0) if max_width or max_height: - url_frags['max_size'] = '/%sx%s' % (max_width, max_height) + max_size = '%sx%s' % (max_width, max_height) - img = '' - return ir_qweb.HTMLSafe(img % url_frags) + src = self.pool['website'].image_url(cr, uid, record, field_name, max_size) + img = '' % (classes, src) + return ir_qweb.HTMLSafe(img) local_url_re = re.compile(r'^/(?P[^]]+)/static/(?P.+)$') def from_html(self, cr, uid, model, column, element, context=None): diff --git a/addons/website/models/website.py b/addons/website/models/website.py index a1414f204bc..691fc3e2091 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -4,13 +4,13 @@ import contextlib import datetime import hashlib import inspect -import itertools import logging import math import mimetypes import unicodedata import os import re +import time import urlparse from PIL import Image @@ -25,8 +25,7 @@ except ImportError: import openerp from openerp.osv import orm, osv, fields -from openerp.tools import html_escape as escape -from openerp.tools import ustr as ustr +from openerp.tools import html_escape as escape, ustr, image_resize_and_sharpen, image_save_for_web from openerp.tools.safe_eval import safe_eval from openerp.addons.web.http import request @@ -513,7 +512,7 @@ class website(osv.osv): response.data = f.read() return response.make_conditional(request.httprequest) - def _image(self, cr, uid, model, id, field, response, max_width=maxint, max_height=maxint, context=None): + def _image(self, cr, uid, model, id, field, response, max_width=maxint, max_height=maxint, cache=None, context=None): """ Fetches the requested field and ensures it does not go above (max_width, max_height), resizing it if necessary. @@ -565,6 +564,10 @@ class website(osv.osv): response.set_etag(hashlib.sha1(record[field]).hexdigest()) response.make_conditional(request.httprequest) + if cache: + response.cache_control.max_age = cache + response.expires = int(time.time() + cache) + # conditional request match if response.status_code == 304: return response @@ -585,14 +588,22 @@ class website(osv.osv): if w < max_w and h < max_h: response.data = data else: - image.thumbnail((max_w, max_h), Image.ANTIALIAS) - image.save(response.stream, image.format) + size = (max_w, max_h) + img = image_resize_and_sharpen(image, size) + image_save_for_web(img, response.stream, format=image.format) # invalidate content-length computed by make_conditional as # writing to response.stream does not do it (as of werkzeug 0.9.3) del response.headers['Content-Length'] return response + def image_url(self, cr, uid, record, field, size=None, context=None): + """Returns a local url that points to the image field of a given browse record.""" + model = record._name + id = '%s_%s' % (record.id, hashlib.sha1(record.sudo().write_date).hexdigest()[0:7]) + size = '' if size is None else '-%s' % size + return '/website/image/%s-%s-%s%s' % (model, id, field, size) + class website_menu(osv.osv): _name = "website.menu" @@ -668,11 +679,7 @@ class ir_attachment(osv.osv): if attach.url: result[attach.id] = attach.url else: - result[attach.id] = urlplus('/website/image', { - 'model': 'ir.attachment', - 'field': 'datas', - 'id': attach.id - }) + result[attach.id] = self.pool['website'].image_url(cr, uid, attach, 'datas') return result def _datas_checksum(self, cr, uid, ids, name, arg, context=None): return dict( diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index ab6abed522b..50c939a0803 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -1387,6 +1387,10 @@ 'click button.filepicker': function () { this.$('input[type=file]').click(); }, + 'click .js_disable_optimization': function () { + this.$('input[name="disable_optimization"]').val('1'); + this.$('button.filepicker').click(); + }, 'change input[type=file]': 'file_selection', 'submit form': 'form_submit', 'change input.url': "change_input", diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 24d54ec017e..13b80b1e706 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -204,7 +204,17 @@
- + +
+ + + +
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index b8b9234a60b..eb353023cff 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -88,8 +88,8 @@ - - + + @@ -150,6 +150,9 @@
+ + + @@ -177,8 +180,11 @@ diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index 9d9fdb9ba03..d28ae7375d2 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -123,7 +123,7 @@