[IMP] extract & simplify image serving, deduplicate a bit between image fields and attachments

bzr revid: xmo@openerp.com-20131002095917-coux4ikqfthqhlo7
This commit is contained in:
Xavier Morel 2013-10-02 11:59:17 +02:00
parent 81b0266ac3
commit 3b686d7773
1 changed files with 87 additions and 73 deletions

View File

@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
import base64
import cStringIO
import contextlib
import hashlib
import json
import logging
import os
import datetime
from sys import maxint
import psycopg2
import werkzeug
@ -230,79 +234,6 @@ class Website(openerp.addons.web.controllers.main.Home):
window.parent['%s'](%s, %s);
</script>""" % (func, json.dumps(url), json.dumps(message))
@website.route('/website/attachment/<int:id>', type='http', auth="admin")
def attachment(self, id):
# TODO: provide actual thumbnails?
# FIXME: can't use Binary.image because auth=user and website attachments need to be public
attachment = request.registry['ir.attachment'].browse(
request.cr, request.uid, id, request.context)
buf = cStringIO.StringIO(base64.decodestring(attachment.datas))
image = Image.open(buf)
mime = PIL_MIME_MAPPING[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
@website.route('/website/image', type='http', auth="public")
def image(self, model, id, field, **kw):
last_update = '__last_update'
Model = request.registry[model]
headers = [('Content-Type', 'image/png')]
etag = request.httprequest.headers.get('If-None-Match')
hashed_session = hashlib.md5(request.session_id).hexdigest()
retag = hashed_session
try:
id = int(id)
ids = Model.search(request.cr, request.uid, [('id', '=', id)], context=request.context)
if not ids:
id = Model.search(request.cr, openerp.SUPERUSER_ID, [('id', '=', id), ('website_published', '=', True)], context=request.context)[0]
if etag:
date = Model.read(request.cr, openerp.SUPERUSER_ID, [id], [last_update], request.context)[0].get(last_update)
if hashlib.md5(date).hexdigest() == etag:
return werkzeug.wrappers.Response(status=304)
res = Model.read(request.cr, openerp.SUPERUSER_ID, [id], [last_update, field], context=request.context)[0]
retag = hashlib.md5(res.get(last_update)).hexdigest()
image_base64 = res.get(field)
if kw.get('resize'):
resize = kw.get('resize').split(',')
if len(resize) == 2 and int(resize[0]) and int(resize[1]):
width = int(resize[0])
height = int(resize[1])
# resize maximum 500*500
if width > 500:
width = 500
if height > 500:
height = 500
image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
image_data = base64.b64decode(image_base64)
except Exception:
image_data = open(os.path.join(http.addons_manifest['web']['addons_path'], 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
headers.append(('ETag', retag))
headers.append(('Content-Length', len(image_data)))
try:
ncache = int(kw.get('cache'))
headers.append(('Cache-Control', 'no-cache' if ncache == 0 else 'max-age=%s' % (ncache)))
except:
pass
return request.make_response(image_data, headers)
@website.route(['/website/publish'], type='json', auth="public")
def publish(self, id, object):
_id = int(id)
@ -327,4 +258,87 @@ class Website(openerp.addons.web.controllers.main.Home):
def sitemap(self):
return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
class Images(http.Controller):
@website.route('/website/image', auth="public")
def image(self, model, id, field):
Model = request.registry[model]
response = werkzeug.wrappers.Response()
id = int(id)
ids = Model.search(request.cr, request.uid,
[('id', '=', id)], context=request.context)\
or Model.search(request.cr, openerp.SUPERUSER_ID,
[('id', '=', id), ('website_published', '=', True)], context=request.context)
if not ids:
# file_open may return a StringIO. StringIO can be closed but are
# not context managers in Python 2 though that is fixed in 3
with contextlib.closing(openerp.tools.misc.file_open(
os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
mode='rb')) as f:
response.set_data(f.read())
return response
concurrency = '__last_update'
[record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
[concurrency, field], context=request.context)
if concurrency in record:
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
try:
response.last_modified = datetime.datetime.strptime(
record[concurrency], server_format + '.%f')
except ValueError:
# just in case we have a timestamp without microseconds
response.last_modified = datetime.datetime.strptime(
record[concurrency], server_format)
# FIXME: no field in record?
response.set_etag(hashlib.sha1(record[field]).hexdigest())
response.make_conditional(request.httprequest)
# conditional request match
if response.status_code == 304:
return response
return self.set_image_data(response, record[field].decode('base64'))
# FIXME: auth
# FIXME: delegate to image?
@website.route('/website/attachment/<int:id>', auth='admin')
def attachment(self, id):
attachment = request.registry['ir.attachment'].browse(
request.cr, request.uid, id, request.context)
return self.set_image_data(
werkzeug.wrappers.Response(),
attachment.datas.decode('base64'),
fit=IMAGE_LIMITS,)
def set_image_data(self, response, data, fit=(maxint, maxint)):
""" Sets an inferred mime type on the response object, and puts the
provided image's data in it, possibly after resizing if requested
Returns the response object after setting its mime and content, so
the result of ``get_final_image`` can be returned directly.
"""
buf = cStringIO.StringIO(data)
# FIXME: unknown format or not an image
image = Image.open(buf)
response.mimetype = PIL_MIME_MAPPING[image.format]
w, h = image.size
max_w, max_h = fit
if w < max_w and h < max_h:
response.set_data(data)
return response
image.thumbnail(fit, Image.ANTIALIAS)
image.save(response.stream, image.format)
return response
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: