Convert UnidentifiedImageError from PIL into validation error
The image conversion that are done as preprocessing of the user input should be considered as data validation. Closes #14084
This commit is contained in:
@@ -12,6 +12,7 @@ except ImportError:
|
||||
PIL = None
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
MatchMixin, ModelSQL, ModelView, Unique, fields, sequence_ordered)
|
||||
from trytond.model.exceptions import SQLConstraintError
|
||||
@@ -22,6 +23,8 @@ from trytond.tools import timezone as tz
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Button, StateTransition, StateView, Wizard
|
||||
|
||||
from .exceptions import LogoValidationError
|
||||
|
||||
SIZE_MAX = config.getint('company', 'logo_size_max', default=2048)
|
||||
TIMEZONES = [(z, z) for z in tz.available_timezones()]
|
||||
TIMEZONES += [(None, '')]
|
||||
@@ -118,9 +121,13 @@ class Company(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def _logo_convert(cls, image, **_params):
|
||||
if not PIL:
|
||||
return
|
||||
return image
|
||||
data = io.BytesIO()
|
||||
try:
|
||||
img = PIL.Image.open(io.BytesIO(image))
|
||||
except PIL.UnidentifiedImageError as e:
|
||||
raise LogoValidationError(gettext(
|
||||
'company.msg_company_logo_error'), str(e)) from e
|
||||
img.thumbnail((SIZE_MAX, SIZE_MAX))
|
||||
if img.mode != 'RGBA':
|
||||
img = img.convert('RGBA')
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model.exceptions import ValidationError
|
||||
|
||||
|
||||
class LogoValidationError(ValidationError):
|
||||
pass
|
||||
@@ -6,5 +6,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_company_logo_cache_size_unique">
|
||||
<field name="text">The size of cached company logo must be unique.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_company_logo_error">
|
||||
<field name="text">Can not open or identify the image set for the company logo.</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model.exceptions import ValidationError
|
||||
|
||||
|
||||
class ImageValidationError(ValidationError):
|
||||
pass
|
||||
@@ -6,5 +6,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_image_cache_size_unique">
|
||||
<field name="text">The size of cached image must be unique.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_product_image_error">
|
||||
<field name="text">Can not open or identify the image set for the product.</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -4,9 +4,11 @@ import io
|
||||
import math
|
||||
from urllib.parse import quote, urlencode, urljoin
|
||||
|
||||
import PIL
|
||||
import PIL.Image
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
MatchMixin, ModelSQL, ModelView, Unique, fields, sequence_ordered)
|
||||
from trytond.pool import PoolMeta
|
||||
@@ -16,6 +18,8 @@ from trytond.transaction import Transaction
|
||||
from trytond.url import http_host
|
||||
from trytond.wsgi import Base64Converter
|
||||
|
||||
from .exceptions import ImageValidationError
|
||||
|
||||
if config.getboolean('product', 'image_filestore', default=False):
|
||||
file_id = 'image_id'
|
||||
store_prefix = config.get('product', 'image_prefix', default=None)
|
||||
@@ -162,7 +166,11 @@ class ImageMixin(_ImageMixin):
|
||||
@classmethod
|
||||
def convert(cls, image, **_params):
|
||||
data = io.BytesIO()
|
||||
try:
|
||||
img = PIL.Image.open(io.BytesIO(image))
|
||||
except PIL.UnidentifiedImageError as e:
|
||||
raise ImageValidationError(gettext(
|
||||
'product_image.msg_product_image_error'), str(e)) from e
|
||||
img.thumbnail((SIZE_MAX, SIZE_MAX))
|
||||
if img.mode != 'RGB':
|
||||
img = img.convert('RGBA')
|
||||
|
||||
@@ -14,7 +14,9 @@ except ImportError:
|
||||
PIL = None
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import ModelSQL, Unique, fields
|
||||
from trytond.model.exceptions import ValidationError
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wsgi import Base64Converter
|
||||
@@ -31,6 +33,10 @@ URL_BASE = config.get('web', 'avatar_base', default='')
|
||||
FONT = os.path.join(os.path.dirname(__file__), 'fonts', 'karla.ttf')
|
||||
|
||||
|
||||
class AvatarValidationError(ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ImageMixin:
|
||||
__slots__ = ()
|
||||
image = fields.Binary(
|
||||
@@ -118,7 +124,11 @@ class Avatar(ImageMixin, ResourceMixin, ModelSQL):
|
||||
if not PIL or not image:
|
||||
return image
|
||||
data = io.BytesIO()
|
||||
try:
|
||||
img = Image.open(io.BytesIO(image))
|
||||
except PIL.UnidentifiedImageError as e:
|
||||
raise AvatarValidationError(gettext(
|
||||
'ir.msg_avatar_image_error'), str(e)) from e
|
||||
width, height = img.size
|
||||
size = min(width, height)
|
||||
img = img.crop((
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from .avatar import AvatarValidationError
|
||||
from .chat import InvalidEMailError as ChatInvalidEmailError
|
||||
from .lang import DateError as LanguageDateError
|
||||
from .lang import DeleteDefaultError as LanguageDeleteDefaultError
|
||||
@@ -14,6 +15,7 @@ from .translation import OverriddenError as TranslationOverriddenError
|
||||
from .trigger import ConditionError as TriggerConditionError
|
||||
|
||||
__all__ = [
|
||||
AvatarValidationError,
|
||||
ChatInvalidEmailError,
|
||||
DeactivateDependencyError,
|
||||
LanguageDateError,
|
||||
|
||||
@@ -418,6 +418,9 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_avatar_size_unique">
|
||||
<field name="text">The size of an avatar must be unique.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_avatar_image_error">
|
||||
<field name="text">Can not open or identify the image set as avatar.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_avatar">
|
||||
<field name="text">Avatar</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user