From 6130ed3ce0fbc6da5dea27ca2a005fc8d4445588 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 14 May 2014 16:32:36 +0200 Subject: [PATCH] [IMP] website: refactor website_image controller (and placeholder), move to model to make it reusable It is "almost" reusable because it still depends on the global `request` variable, this could be solved by making it an explicit parameter. --- addons/website/controllers/main.py | 82 +---------------------- addons/website/models/website.py | 100 +++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 79 deletions(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index c064fdcbcd9..79e5dfb0147 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -1,18 +1,12 @@ # -*- coding: utf-8 -*- import cStringIO -import contextlib -import hashlib import json import logging import math -import os -import datetime import re from sys import maxint -import werkzeug -import werkzeug.exceptions import werkzeug.utils import werkzeug.wrappers from PIL import Image @@ -336,13 +330,7 @@ class Website(openerp.addons.web.controllers.main.Home): return request.website.kanban_col(**post) def placeholder(self, response): - # 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.data = f.read() - return response.make_conditional(request.httprequest) + return request.registry['website']._image_placeholder(response) @http.route([ '/website/image', @@ -366,74 +354,10 @@ class Website(openerp.addons.web.controllers.main.Home): The requested field is assumed to be base64-encoded image data in all cases. """ - Model = request.registry[model] - response = werkzeug.wrappers.Response() + return request.registry['website']._image( + request.cr, request.uid, model, id, field, response) - id = int(id) - - ids = Model.search(request.cr, request.uid, - [('id', '=', id)], context=request.context) - if not ids and 'website_published' in Model._all_columns: - ids = Model.search(request.cr, openerp.SUPERUSER_ID, - [('id', '=', id), ('website_published', '=', True)], context=request.context) - - if not ids: - return self.placeholder(response) - - presized = '%s_big' % field - concurrency = '__last_update' - [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id], - [concurrency, field, presized], - 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) - - # Field does not exist on model or field set to False - if not record.get(field): - # FIXME: maybe a field which does not exist should be a 404? - return self.placeholder(response) - - response.set_etag(hashlib.sha1(record[field]).hexdigest()) - response.make_conditional(request.httprequest) - - # conditional request match - if response.status_code == 304: - return response - - data = (record.get(presized) or record[field]).decode('base64') - - image = Image.open(cStringIO.StringIO(data)) - response.mimetype = Image.MIME[image.format] - - # record provides a pre-resized version of the base field, use that - # directly - if record.get(presized): - response.data = data - return response - - fit = int(max_width), int(max_height) - w, h = image.size - max_w, max_h = fit - - if w < max_w and h < max_h: - response.data = data - else: - image.thumbnail(fit, Image.ANTIALIAS) - image.save(response.stream, 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 #------------------------------------------------------ # Server actions diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 584dc4b6105..e0aac6dbbbf 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -1,13 +1,20 @@ # -*- coding: utf-8 -*- +import cStringIO +import contextlib +import datetime import hashlib import inspect import itertools import logging import math import mimetypes +import os import re import urlparse +from PIL import Image +from sys import maxint + import werkzeug import werkzeug.exceptions import werkzeug.utils @@ -472,6 +479,99 @@ class website(osv.osv): html += request.website._render(template, {'object_id': object_id}) return html + def _image_placeholder(self, response): + # 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.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): + """ Fetches the requested field and ensures it does not go above + (max_width, max_height), resizing it if necessary. + + Resizing is bypassed if the object provides a $field_big, which will + be interpreted as a pre-resized version of the base field. + + If the record is not found or does not have the requested field, + returns a placeholder image via :meth:`~._image_placeholder`. + + Sets and checks conditional response parameters: + * :mailheader:`ETag` is always set (and checked) + * :mailheader:`Last-Modified is set iif the record has a concurrency + field (``__last_update``) + + The requested field is assumed to be base64-encoded image data in + all cases. + """ + Model = self.pool[model] + id = int(id) + + ids = Model.search(cr, uid, + [('id', '=', id)], context=context) + if not ids and 'website_published' in Model._all_columns: + ids = Model.search(cr, openerp.SUPERUSER_ID, + [('id', '=', id), ('website_published', '=', True)], context=context) + if not ids: + return self._image_placeholder(response) + + presized = '%s_big' % field + concurrency = '__last_update' + [record] = Model.read(cr, openerp.SUPERUSER_ID, [id], + [concurrency, field, presized], + context=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) + + # Field does not exist on model or field set to False + if not record.get(field): + # FIXME: maybe a field which does not exist should be a 404? + return self._image_placeholder(response) + + response.set_etag(hashlib.sha1(record[field]).hexdigest()) + response.make_conditional(request.httprequest) + + # conditional request match + if response.status_code == 304: + return response + + data = (record.get(presized) or record[field]).decode('base64') + + image = Image.open(cStringIO.StringIO(data)) + response.mimetype = Image.MIME[image.format] + + # record provides a pre-resized version of the base field, use that + # directly + if record.get(presized): + response.data = data + return response + + fit = int(max_width), int(max_height) + w, h = image.size + max_w, max_h = fit + + if w < max_w and h < max_h: + response.data = data + else: + image.thumbnail(fit, Image.ANTIALIAS) + image.save(response.stream, 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 + + class website_menu(osv.osv): _name = "website.menu" _description = "Website Menu"