summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThanhDodeurOdoo <tso@odoo.com>2018-11-26 10:08:01 +0000
committerThanhDodeurOdoo <tso@odoo.com>2018-12-06 19:09:33 +0000
commit7d85ab1eac6dbf33d56c6a1f8bf7e9e12bb8008e (patch)
treef977a392fe9e9fe9a21c06d997c0d5b877cbce76
parent89f20d2c297d99d4eb0ded8be78fbf86b8ec45d3 (diff)
[REF] base, *: refactor binary_content
*: tools, web, website, website_forum, mail, im_livechat This commit refactors ir_http to make it more readable and flexible. Move the resize function of web/image to odoo.tools Co-authored-by: XavierDo <xdo@odoo.com> Co-authored-by: Antony Lesuisse <al@openerp.com> closes: #28563 task: #1908896
-rw-r--r--addons/im_livechat/controllers/main.py3
-rw-r--r--addons/mail/controllers/main.py4
-rw-r--r--addons/web/controllers/main.py71
-rw-r--r--addons/website/models/ir_http.py21
-rw-r--r--addons/website_forum/controllers/main.py4
-rw-r--r--odoo/addons/base/models/ir_http.py251
-rw-r--r--odoo/addons/base/tests/test_ir_http.py7
-rw-r--r--odoo/tools/image.py31
8 files changed, 182 insertions, 210 deletions
diff --git a/addons/im_livechat/controllers/main.py b/addons/im_livechat/controllers/main.py
index abe0e17bb0c..86553625f4d 100644
--- a/addons/im_livechat/controllers/main.py
+++ b/addons/im_livechat/controllers/main.py
@@ -6,7 +6,6 @@ import base64
from odoo import http, _
from odoo.http import request
from odoo.addons.base.models.assetsbundle import AssetsBundle
-from odoo.addons.web.controllers.main import binary_content
class LivechatController(http.Controller):
@@ -22,7 +21,7 @@ class LivechatController(http.Controller):
if isinstance(mock_attachment, list): # suppose that CSS asset will not required to be split in pages
mock_attachment = mock_attachment[0]
# can't use /web/content directly because we don't have attachment ids (attachments must be created)
- status, headers, content = binary_content(id=mock_attachment.id, unique=asset.checksum)
+ status, headers, content = request.env['ir.http'].binary_content(id=mock_attachment.id, unique=asset.checksum)
content_base64 = base64.b64decode(content) if content else ''
headers.append(('Content-Length', len(content_base64)))
return request.make_response(content_base64, headers)
diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py
index 6694fbf72b1..38965dcf79b 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -9,7 +9,6 @@ import werkzeug
from werkzeug import url_encode
from odoo import api, http, registry, SUPERUSER_ID, _
-from odoo.addons.web.controllers.main import binary_content
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.tools import consteq
@@ -216,7 +215,8 @@ class MailController(http.Controller):
# if the current user has access to the document, get the partner avatar as sudo()
request.env[res_model].browse(res_id).check_access_rule('read')
if partner_id in request.env[res_model].browse(res_id).sudo().exists().message_ids.mapped('author_id').ids:
- status, headers, _content = binary_content(model='res.partner', id=partner_id, field='image_medium', default_mimetype='image/png', env=request.env(user=SUPERUSER_ID))
+ status, headers, _content = request.env['ir.http'].sudo().binary_content(
+ model='res.partner', id=partner_id, field='image_medium', default_mimetype='image/png')
# binary content return an empty string and not a placeholder if obj[field] is False
if _content != '':
content = _content
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 64cf2ca9ac5..57c86a2c6d6 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -36,7 +36,7 @@ import odoo
import odoo.modules.registry
from odoo.api import call_kw, Environment
from odoo.modules import get_resource_path
-from odoo.tools import crop_image, topological_sort, html_escape, pycompat
+from odoo.tools import limited_image_resize, topological_sort, html_escape, pycompat
from odoo.tools.mimetypes import guess_mimetype
from odoo.tools.translate import _
from odoo.tools.misc import str2bool, xlwt, file_open
@@ -424,16 +424,6 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False):
res["children"] = kids
return res
-def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', unique=False,
- filename=None, filename_field='datas_fname', download=False, mimetype=None,
- default_mimetype='application/octet-stream', related_id=None, access_mode=None, access_token=None,
- env=None):
- return request.registry['ir.http'].binary_content(
- xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename,
- filename_field=filename_field, download=download, mimetype=mimetype,
- default_mimetype=default_mimetype, related_id=related_id, access_mode=access_mode, access_token=access_token,
- env=env)
-
#----------------------------------------------------------
# Odoo Web web Controllers
#----------------------------------------------------------
@@ -1029,18 +1019,14 @@ class Binary(http.Controller):
'/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public")
def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas',
filename=None, filename_field='datas_fname', unique=None, mimetype=None,
- download=None, data=None, token=None, access_token=None, related_id=None, access_mode=None,
- **kw):
- status, headers, content = binary_content(
+ download=None, data=None, token=None, access_token=None, **kw):
+
+ status, headers, content = request.env['ir.http'].binary_content(
xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename,
- filename_field=filename_field, download=download, mimetype=mimetype,
- access_token=access_token, related_id=related_id, access_mode=access_mode)
- if status == 304:
- response = werkzeug.wrappers.Response(status=status, headers=headers)
- elif status == 301:
- return werkzeug.utils.redirect(content, code=301)
- elif status != 200:
- response = request.not_found()
+ filename_field=filename_field, download=download, mimetype=mimetype, access_token=access_token)
+
+ if status != 200:
+ return request.env['ir.http']._response_by_status(status, headers, content)
else:
content_base64 = base64.b64decode(content)
headers.append(('Content-Length', len(content_base64)))
@@ -1068,39 +1054,18 @@ class Binary(http.Controller):
'/web/image/<int:id>-<string:unique>/<int:width>x<int:height>/<string:filename>'], type='http', auth="public")
def content_image(self, xmlid=None, model='ir.attachment', id=None, field='datas',
filename_field='datas_fname', unique=None, filename=None, mimetype=None,
- download=None, width=0, height=0, crop=False, related_id=None, access_mode=None,
- access_token=None, avoid_if_small=False, upper_limit=False, signature=False):
- status, headers, content = binary_content(
+ download=None, width=0, height=0, crop=False, access_token=None, avoid_if_small=False,
+ upper_limit=False, **kw):
+ status, headers, content = request.env['ir.http'].binary_content(
xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename,
filename_field=filename_field, download=download, mimetype=mimetype,
- default_mimetype='image/png', related_id=related_id, access_mode=access_mode, access_token=access_token)
- if status == 304:
- return werkzeug.wrappers.Response(status=304, headers=headers)
- elif status == 301:
- return werkzeug.utils.redirect(content, code=301)
- elif status != 200 and download:
- return request.not_found()
-
- if headers and dict(headers).get('Content-Type', '') == 'image/svg+xml': # we shan't resize svg images
- height = 0
- width = 0
- else:
- height = int(height or 0)
- width = int(width or 0)
-
- if crop and (width or height):
- content = crop_image(content, type='center', size=(width, height), ratio=(1, 1))
-
- elif content and (width or height):
- if not upper_limit:
- # resize maximum 500*500
- if width > 500:
- width = 500
- if height > 500:
- height = 500
- content = odoo.tools.image_resize_image(base64_source=content, size=(width or None, height or None),
- encoding='base64', upper_limit=upper_limit,
- avoid_if_small=avoid_if_small)
+ default_mimetype='image/png', access_token=access_token)
+
+ if status != 200 and download:
+ return request.env['ir.http']._response_by_status(status, headers, content)
+
+ content = limited_image_resize(
+ content, width=width, height=height, crop=crop, upper_limit=upper_limit, avoid_if_small=avoid_if_small)
if content:
image_base64 = base64.b64decode(content)
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 7ca7563159b..25becf9749c 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -237,25 +237,22 @@ class Http(models.AbstractModel):
html = request.env['ir.ui.view'].render_template('website.http_error', values)
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
- @classmethod
- def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas',
+ def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas',
unique=False, filename=None, filename_field='datas_fname', download=False,
mimetype=None, default_mimetype='application/octet-stream',
- access_token=None, related_id=None, access_mode=None, env=None):
- env = env or request.env
+ access_token=None):
obj = None
if xmlid:
- obj = cls._xmlid_to_obj(env, xmlid)
- elif id and model in env:
- obj = env[model].browse(int(id))
+ obj = self._xmlid_to_obj(self.env, xmlid)
+ elif id and model in self.env:
+ obj = self.env[model].browse(int(id))
if obj and 'website_published' in obj._fields:
- if env[obj._name].sudo().search([('id', '=', obj.id), ('website_published', '=', True)]):
- env = env(user=SUPERUSER_ID)
- return super(Http, cls).binary_content(
+ if self.env[obj._name].sudo().search([('id', '=', obj.id), ('website_published', '=', True)]):
+ self.sudo()
+ return super(Http, self).binary_content(
xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename,
filename_field=filename_field, download=download, mimetype=mimetype,
- default_mimetype=default_mimetype, access_token=access_token, related_id=related_id,
- access_mode=access_mode, env=env)
+ default_mimetype=default_mimetype, access_token=access_token)
@classmethod
def _xmlid_to_obj(cls, env, xmlid):
diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py
index 05c00e8df93..42b1e892fcc 100644
--- a/addons/website_forum/controllers/main.py
+++ b/addons/website_forum/controllers/main.py
@@ -12,7 +12,6 @@ from datetime import datetime
from odoo import http, modules, SUPERUSER_ID, tools, _
from odoo.addons.http_routing.models.ir_http import slug
-from odoo.addons.web.controllers.main import binary_content
from odoo.addons.website.models.ir_http import sitemap_qs2dom
from odoo.http import request
@@ -591,7 +590,8 @@ class WebsiteForum(http.Controller):
@http.route(['/forum/user/<int:user_id>/avatar'], type='http', auth="public", website=True, sitemap=False)
def user_avatar(self, user_id=0, **post):
- status, headers, content = binary_content(model='res.users', id=user_id, field='image_medium', default_mimetype='image/png', env=request.env(user=SUPERUSER_ID))
+ status, headers, content = request.env['ir.http'].sudo().binary_content(
+ model='res.users', id=user_id, field='image_medium', default_mimetype='image/png')
if not content:
img_path = modules.get_module_resource('web', 'static/src/img', 'placeholder.png')
diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py
index 5d6e84a59b3..1cc910b58f9 100644
--- a/odoo/addons/base/models/ir_http.py
+++ b/odoo/addons/base/models/ir_http.py
@@ -74,6 +74,10 @@ class IrHttp(models.AbstractModel):
_name = 'ir.http'
_description = "HTTP Routing"
+ #------------------------------------------------------
+ # Routing map
+ #------------------------------------------------------
+
@classmethod
def _get_converters(cls):
return {'model': ModelConverter, 'models': ModelsConverter, 'int': SignedIntConverter}
@@ -236,176 +240,167 @@ class IrHttp(models.AbstractModel):
if hasattr(cls, '_routing_map'):
del cls._routing_map
- @classmethod
- def content_disposition(cls, filename):
- return content_disposition(filename)
+ #------------------------------------------------------
+ # Binary server
+ #------------------------------------------------------
@classmethod
def _xmlid_to_obj(cls, env, xmlid):
return env.ref(xmlid, False)
- @classmethod
- def _check_access_mode(cls, env, id, access_mode, model, access_token=None, related_id=None):
- """
- Implemented by each module to define an additional way to check access.
-
- :param env: the env of binary_content
- :param id: id of the record from which to fetch the binary
- :param access_mode: typically a string that describes the behaviour of the custom check
- :param model: the model of the object for which binary_content was called
- :param related_id: optional id to check security.
- :return: True if the test passes, else False.
- """
- return False
-
- @classmethod
- def _get_special_models(cls):
- """
- :return: the set of models that have to pass through several specific fields checks:
- used for the 'url' and the 'access_token' fields.
- """
- return {'ir.attachment'}
-
- @classmethod
- def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas',
- unique=False, filename=None, filename_field='datas_fname', download=False,
- mimetype=None, default_mimetype='application/octet-stream',
- access_token=None, related_id=None, access_mode=None, env=None):
- """ Get file, attachment or downloadable content
-
- If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the
- binary field (via ``default_get``), otherwise fetches the field for
- that precise record.
-
- :param str xmlid: xmlid of the record
- :param str model: name of the model to fetch the binary from
- :param int id: id of the record from which to fetch the binary
- :param str field: binary field
- :param bool unique: add a max-age for the cache control
- :param str filename: choose a filename
- :param str filename_field: if not create an filename with model-id-field
- :param bool download: apply headers to download the file
- :param str mimetype: mintype of the field (for headers)
- :param related_id: the id of another record used for custom_check
- :param access_mode: if truthy, will call custom_check to fetch the object that contains the binary.
- :param str default_mimetype: default mintype if no mintype found
- :param str access_token: optional token for unauthenticated access
- only available for ir.attachment
- :param Environment env: by default use request.env
- :returns: (status, headers, content)
- """
- env = env or request.env
- attachment_models = cls._get_special_models()
+ def _get_record_and_check(self, xmlid=None, model=None, id=None, field='datas', access_token=None):
# get object and content
- obj = None
+ record = None
if xmlid:
- obj = cls._xmlid_to_obj(env, xmlid)
- elif id and model in env.registry:
- obj = env[model].browse(int(id))
+ record = self._xmlid_to_obj(self.env, xmlid)
+ elif id and model in self.env:
+ record = self.env[model].browse(int(id))
+
# obj exists
- if not obj or not obj.exists() or field not in obj:
- return (404, [], None)
+ if not record or not record.exists() or field not in record:
+ return None, 404
# access token grant access
- if model in attachment_models and access_token:
- obj = obj.sudo()
- if access_mode:
- if not cls._check_access_mode(env, id, access_mode, model, access_token=access_token,
- related_id=related_id):
- return (403, [], None)
- elif not consteq(obj.access_token or u'', access_token):
- return (403, [], None)
+ if model == 'ir.attachment' and access_token:
+ record = record.sudo()
+ if not consteq(record.access_token or '', access_token):
+ return None, 403
# check read access
try:
- last_update = obj['__last_update']
+ last_update = record['__last_update']
except AccessError:
- return (403, [], None)
+ return None, 403
- status, headers, content = None, [], None
+ return record, 200
- # attachment by url check
- module_resource_path = None
- if model in attachment_models and obj.type == 'url' and obj.url:
- url_match = re.match("^/(\w+)/(.+)$", obj.url)
+ @classmethod
+ def _binary_ir_attachment_redirect_content(cls, record, default_mimetype='application/octet-stream'):
+ # mainly used for theme images attachemnts
+ status = content = filename = mimetype = filehash = None
+ if record.type == 'url' and record.url:
+ # if url in in the form /somehint server locally
+ url_match = re.match("^/(\w+)/(.+)$", record.url)
if url_match:
module = url_match.group(1)
module_path = get_module_path(module)
module_resource_path = get_resource_path(module, url_match.group(2))
+
if module_path and module_resource_path:
module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/'
module_resource_path = os.path.normpath(module_resource_path)
if module_resource_path.startswith(module_path):
with open(module_resource_path, 'rb') as f:
content = base64.b64encode(f.read())
- last_update = str(os.path.getmtime(module_resource_path))
+ status = 200
+ filename = os.path.basename(module_resource_path)
+ mimetype = guess_mimetype(base64.b64decode(content), default=default_mimetype)
+ filehash = '"%s"' % hashlib.md5(pycompat.to_text(content).encode('utf-8')).hexdigest()
+ else:
+ status = 301
+ content = record.url
- if not module_resource_path:
- module_resource_path = obj.url
- if not content:
- status = 301
- content = module_resource_path
- else:
- content = obj[field] or ''
+
+ return status, content, filename, mimetype, filehash
+
+ def _binary_record_content(
+ self, record, field='datas', filename=None,
+ filename_field='datas_fname', default_mimetype='application/octet-stream'):
+
+ model = record._name
+ mimetype = 'mimetype' in record and record.mimetype or False
+ content = None
+ filehash = 'checksum' in record and record['checksum'] or False
+
+ field_def = record._fields[field]
+ if field_def.type == 'binary' and field_def.attachment:
+ field_attachment = self.env['ir.attachment'].search_read(domain=[('res_model', '=', model), ('res_id', '=', record.id), ('res_field', '=', field)], fields=['datas', 'mimetype', 'checksum'], limit=1)
+ if field_attachment:
+ mimetype = field_attachment[0]['mimetype']
+ content = field_attachment[0]['datas']
+ filehash = field_attachment[0]['checksum']
+
+ if not content:
+ content = record[field] or ''
# filename
if not filename:
- if filename_field in obj:
- filename = obj[filename_field]
- elif module_resource_path:
- filename = os.path.basename(module_resource_path)
+ if filename_field in record:
+ filename = record[filename_field]
else:
- filename = "%s-%s-%s" % (obj._name, obj.id, field)
+ filename = "%s-%s-%s" % (record._name, record.id, field)
- # mimetype
- mimetype = 'mimetype' in obj and obj.mimetype or False
if not mimetype:
- if filename:
- mimetype = mimetypes.guess_type(filename)[0]
- if not mimetype and getattr(env[model]._fields[field], 'attachment', False):
- # for binary fields, fetch the ir_attachement for mimetype check
- attach_mimetype = env['ir.attachment'].search_read(domain=[('res_model', '=', model), ('res_id', '=', id), ('res_field', '=', field)], fields=['mimetype'], limit=1)
- mimetype = attach_mimetype and attach_mimetype[0]['mimetype']
- if not mimetype:
- mimetype = guess_mimetype(base64.b64decode(content), default=default_mimetype)
+ mimetype = guess_mimetype(base64.b64decode(content), default=default_mimetype)
- headers += [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff')]
+ if not filehash:
+ filehash = '"%s"' % hashlib.md5(pycompat.to_text(content).encode('utf-8')).hexdigest()
+ status = 200 if content else 404
+ return status, content, filename, mimetype, filehash
+
+ def _binary_set_headers(self, status, content, filename, mimetype, unique, filehash=None, download=False):
+ headers = [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff')]
# cache
etag = bool(request) and request.httprequest.headers.get('If-None-Match')
- retag = '"%s"' % hashlib.md5(pycompat.to_text(content).encode('utf-8')).hexdigest()
- status = status or (304 if etag == retag else 200)
- headers.append(('ETag', retag))
+ status = status or (304 if filehash and etag == filehash else 200)
+ if filehash:
+ headers.append(('ETag', filehash))
headers.append(('Cache-Control', 'max-age=%s' % (STATIC_CACHE if unique else 0)))
-
# content-disposition default name
if download:
- headers.append(('Content-Disposition', cls.content_disposition(filename)))
+ headers.append(('Content-Disposition', content_disposition(filename)))
+
return (status, headers, content)
+ def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas',
+ unique=False, filename=None, filename_field='datas_fname', download=False,
+ mimetype=None, default_mimetype='application/octet-stream',
+ access_token=None):
+ """ Get file, attachment or downloadable content
-def convert_exception_to(to_type, with_message=False):
- """ Should only be called from an exception handler. Fetches the current
- exception data from sys.exc_info() and creates a new exception of type
- ``to_type`` with the original traceback.
-
- If ``with_message`` is ``True``, sets the new exception's message to be
- the stringification of the original exception. If ``False``, does not
- set the new exception's message. Otherwise, uses ``with_message`` as the
- new exception's message.
-
- :type with_message: str|bool
- """
- etype, original, tb = sys.exc_info()
- try:
- if with_message is False:
- message = None
- elif with_message is True:
- message = str(original)
- else:
- message = str(with_message)
+ If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the
+ binary field (via ``default_get``), otherwise fetches the field for
+ that precise record.
+
+ :param str xmlid: xmlid of the record
+ :param str model: name of the model to fetch the binary from
+ :param int id: id of the record from which to fetch the binary
+ :param str field: binary field
+ :param bool unique: add a max-age for the cache control
+ :param str filename: choose a filename
+ :param str filename_field: if not create an filename with model-id-field
+ :param bool download: apply headers to download the file
+ :param str mimetype: mintype of the field (for headers)
+ :param str default_mimetype: default mintype if no mintype found
+ :param str access_token: optional token for unauthenticated access
+ only available for ir.attachment
+ :returns: (status, headers, content)
+ """
+ record, status = self._get_record_and_check(xmlid=xmlid, model=model, id=id, field=field, access_token=access_token)
+
+ if not record:
+ return (status or 404, [], None)
+
+ content, headers, status = None, [], None
+
+ if record._name == 'ir.attachment':
+ status, content, filename, mimetype, filehash = self._binary_ir_attachment_redirect_content(record, default_mimetype=default_mimetype)
+ if not content:
+ status, content, filename, mimetype, filehash = self._binary_record_content(
+ record, field=field, filename=None, filename_field=filename_field,
+ default_mimetype='application/octet-stream')
+
+ status, headers, content = self._binary_set_headers(
+ status, content, filename, mimetype, unique, filehash=False, download=download)
+
+ return status, headers, content
- raise pycompat.reraise(to_type, to_type(message), tb)
- except to_type as e:
- return e
+ def _response_by_status(self, status, headers, content):
+ if status == 304:
+ return werkzeug.wrappers.Response(status=status, headers=headers)
+ elif status == 301:
+ return werkzeug.utils.redirect(content, code=301)
+ elif status != 200:
+ return request.not_found()
diff --git a/odoo/addons/base/tests/test_ir_http.py b/odoo/addons/base/tests/test_ir_http.py
index 4853c808fe4..f369193741f 100644
--- a/odoo/addons/base/tests/test_ir_http.py
+++ b/odoo/addons/base/tests/test_ir_http.py
@@ -19,7 +19,6 @@ class test_ir_http_mimetype(common.TransactionCase):
id=attachment.id,
mimetype=None,
default_mimetype='application/octet-stream',
- env=self.env
)
mimetype = dict(headers).get('Content-Type')
self.assertEqual(mimetype, 'image/gif')
@@ -35,7 +34,6 @@ class test_ir_http_mimetype(common.TransactionCase):
id=attachment.id,
mimetype=None,
default_mimetype='application/octet-stream',
- env=self.env
)
mimetype = dict(headers).get('Content-Type')
# TODO: fix and change it in master, should be image/gif
@@ -53,7 +51,6 @@ class test_ir_http_mimetype(common.TransactionCase):
id=partner.id,
field='image',
default_mimetype='application/octet-stream',
- env=self.env
)
mimetype = dict(headers).get('Content-Type')
self.assertEqual(mimetype, 'image/gif')
@@ -75,7 +72,6 @@ class test_ir_http_mimetype(common.TransactionCase):
id=prop.id,
field='value_binary',
default_mimetype='application/octet-stream',
- env=self.env
)
mimetype = dict(headers).get('Content-Type')
self.assertEqual(mimetype, 'image/gif')
@@ -92,11 +88,10 @@ class test_ir_http_mimetype(common.TransactionCase):
defaults = {
'id': attachment.id,
'default_mimetype': 'image/gif',
- 'env': public_user.sudo(public_user.id).env,
}
def test_access(**kwargs):
- status, _, _ = self.env['ir.http'].binary_content(
+ status, _, _ = self.env['ir.http'].sudo(public_user.id).binary_content(
**dict(defaults, **kwargs)
)
return status
diff --git a/odoo/tools/image.py b/odoo/tools/image.py
index 0cf79978193..1cec35b8c23 100644
--- a/odoo/tools/image.py
+++ b/odoo/tools/image.py
@@ -75,11 +75,9 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
asked_width, asked_height = size
if upper_limit:
if asked_width:
- if asked_width >= image.size[0]:
- asked_width = image.size[0]
+ asked_width = min(asked_width, image.size[0])
if asked_height:
- if asked_height >= image.size[1]:
- asked_height = image.size[1]
+ asked_height = min(asked_height, image.size[1])
if image.size[0] >= image.size[1]:
asked_height = None
@@ -187,7 +185,7 @@ def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', fi
# ----------------------------------------
# Crop Image
# ----------------------------------------
-def crop_image(data, type='top', ratio=False, size=None, image_format=None):
+def crop_image(data, type='top', ratio=None, size=None, image_format=None):
""" Used for cropping image and create thumbnail
:param data: base64 data of image.
:param type: Used for cropping position possible
@@ -319,6 +317,29 @@ def image_resize_images(vals, big_name='image', medium_name='image_medium', smal
elif big_name in vals or medium_name in vals or small_name in vals:
vals[big_name] = vals[medium_name] = vals[small_name] = False
+def limited_image_resize(content, width=None, height=None, crop=False, upper_limit=False, avoid_if_small=False):
+ """
+ :param content: bytes (should be an image)
+ """
+ if content:
+ signatures = [b'\xFF\xD8\xFF', b'\x89PNG\r\n\x1A\n']
+ decoded_content = base64.b64decode(content)
+ is_image = any(decoded_content.startswith(signature) for signature in signatures)
+ if (width or height) and is_image:
+ height = int(height or 0)
+ width = int(width or 0)
+ if crop:
+ return crop_image(content, type='center', size=(width, height), ratio=(1, 1))
+ else:
+ if not upper_limit:
+ # resize maximum 500*500
+ width = min(width, 500)
+ height = min(height, 500)
+ return image_resize_image(
+ base64_source=content, size=(width or None, height or None), encoding='base64', upper_limit=upper_limit,
+ avoid_if_small=avoid_if_small)
+ return content
+
def image_data_uri(base64_source):
"""This returns data URL scheme according RFC 2397
(https://tools.ietf.org/html/rfc2397) for all kind of supported images