[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:
parent
81b0266ac3
commit
3b686d7773
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue