2022-10-14 13:29:33 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# This file is part of the cashbook-module from m-ds for Tryton.
|
|
|
|
# The COPYRIGHT file at the top level of this repository contains the
|
|
|
|
# full copyright notices and license terms.
|
|
|
|
|
2023-06-06 14:46:03 +00:00
|
|
|
import mimetypes
|
|
|
|
import magic
|
2022-10-14 13:29:33 +00:00
|
|
|
from io import BytesIO
|
2022-11-29 09:30:39 +00:00
|
|
|
from PIL import Image, UnidentifiedImageError
|
2022-10-14 13:29:33 +00:00
|
|
|
from trytond.model import fields
|
2023-06-06 14:46:03 +00:00
|
|
|
from trytond.pool import PoolMeta
|
2022-10-14 13:29:33 +00:00
|
|
|
from trytond.config import config
|
|
|
|
from trytond.exceptions import UserError
|
|
|
|
from trytond.i18n import gettext
|
|
|
|
from trytond.pyson import Eval, Bool
|
|
|
|
from trytond.modules.cashbook.line import STATES, DEPENDS
|
|
|
|
|
|
|
|
|
|
|
|
store_prefix = config.get('cashbook', 'store_prefix', default='cashbook')
|
|
|
|
image_limit = config.get('cashbook', 'image_max_pixel', default='2000')
|
2023-06-06 14:46:03 +00:00
|
|
|
try:
|
2022-10-14 13:29:33 +00:00
|
|
|
image_limit = int(image_limit)
|
|
|
|
if image_limit < 100:
|
|
|
|
image_limit = 100
|
|
|
|
if image_limit > 10000:
|
|
|
|
image_limit = 10000
|
2023-06-06 14:46:03 +00:00
|
|
|
except Exception:
|
2022-10-14 13:29:33 +00:00
|
|
|
image_limit = 2000
|
|
|
|
|
|
|
|
|
2022-12-30 22:02:04 +00:00
|
|
|
STATES2 = {}
|
|
|
|
STATES2.update(STATES)
|
|
|
|
DEPENDS2 = []
|
|
|
|
DEPENDS2.extend(DEPENDS)
|
|
|
|
|
|
|
|
|
2022-10-14 13:29:33 +00:00
|
|
|
class Line(metaclass=PoolMeta):
|
|
|
|
__name__ = 'cashbook.line'
|
|
|
|
|
2023-06-06 14:46:03 +00:00
|
|
|
media = fields.Binary(
|
|
|
|
string='Image of PDF', filename='media_name', file_id='media_id',
|
|
|
|
store_prefix=store_prefix, states=STATES2, depends=DEPENDS2)
|
|
|
|
media_name = fields.Char(
|
|
|
|
string='File name',
|
2022-10-14 13:29:33 +00:00
|
|
|
states={
|
|
|
|
'required': Bool(Eval('media')),
|
2022-12-30 22:02:04 +00:00
|
|
|
'readonly': STATES2['readonly'],
|
|
|
|
}, depends=DEPENDS2)
|
2022-10-14 13:29:33 +00:00
|
|
|
media_id = fields.Char(string='File ID', readonly=True)
|
|
|
|
media_mime = fields.Char(string='MIME', readonly=True)
|
|
|
|
media_size = fields.Integer(string='File size', readonly=True)
|
2023-06-06 14:46:03 +00:00
|
|
|
media_image = fields.Function(fields.Binary(
|
|
|
|
string='Image', readonly=True,
|
2023-01-23 12:11:54 +00:00
|
|
|
states={
|
2023-06-06 14:46:03 +00:00
|
|
|
'invisible': ~Eval('media_mime', '').in_([
|
|
|
|
'image/png', 'image/jpg', 'image/jpeg']),
|
|
|
|
}, depends=['media_mime']), 'on_change_with_media_image')
|
2023-01-23 12:11:54 +00:00
|
|
|
|
|
|
|
@fields.depends('media', 'media_mime')
|
|
|
|
def on_change_with_media_image(self, name=True):
|
|
|
|
""" return binary if its a image
|
|
|
|
"""
|
|
|
|
if (self.media_mime or '-').startswith('image/'):
|
|
|
|
return self.media
|
2022-10-14 13:29:33 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _identify_file(cls, data, mime=True):
|
|
|
|
""" get file-type
|
|
|
|
"""
|
|
|
|
return magic.from_buffer(data, mime=mime)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _hr_file_size(cls, num, suffix="B"):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
|
|
|
if abs(num) < 1024.0:
|
|
|
|
return f"{num:3.1f}{unit}{suffix}"
|
|
|
|
num /= 1024.0
|
|
|
|
return f"{num:.1f}Yi{suffix}"
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def resize_image_file(cls, image_data):
|
|
|
|
""" shrink image 'image_limit' pixel if its bigger
|
|
|
|
"""
|
|
|
|
image_data2 = None
|
|
|
|
with BytesIO(image_data) as fhdl:
|
2023-06-06 14:46:03 +00:00
|
|
|
try:
|
2022-11-29 09:30:39 +00:00
|
|
|
image = Image.open(fhdl, 'r')
|
|
|
|
except UnidentifiedImageError:
|
|
|
|
raise UserError(gettext('cashbook_media.msg_file_unknown_type'))
|
2022-10-14 13:29:33 +00:00
|
|
|
|
|
|
|
(width, height) = image.size
|
|
|
|
if (width > image_limit) or (height > image_limit):
|
|
|
|
|
|
|
|
if width > height:
|
|
|
|
new_size = (image_limit, int(height * image_limit / width))
|
2023-06-06 14:46:03 +00:00
|
|
|
else:
|
2022-10-14 13:29:33 +00:00
|
|
|
new_size = (int(width * image_limit / height), image_limit)
|
|
|
|
|
|
|
|
# resize - fit in (image_limit x image_limit)
|
|
|
|
img2 = image.resize(new_size, Image.LANCZOS)
|
|
|
|
with BytesIO() as fhdl2:
|
|
|
|
img2.save(fhdl2, 'JPEG', optimize=True, quality=80)
|
|
|
|
fhdl2.seek(0)
|
|
|
|
image_data2 = fhdl2.read()
|
|
|
|
del img2
|
|
|
|
del image
|
|
|
|
|
|
|
|
return image_data2
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_media_info(cls, values):
|
|
|
|
""" get mime-type, update file-name
|
|
|
|
"""
|
|
|
|
if len(values['media'] or '') < 100:
|
|
|
|
values['media'] = None
|
|
|
|
values['media_mime'] = None
|
|
|
|
values['media_size'] = None
|
|
|
|
values['media_name'] = None
|
2023-06-06 14:46:03 +00:00
|
|
|
else:
|
2022-10-14 13:29:33 +00:00
|
|
|
values['media_mime'] = cls._identify_file(values['media'][:1024])
|
|
|
|
|
2023-06-06 14:46:03 +00:00
|
|
|
# if its a image, resize it to fit
|
|
|
|
# in (image_limit x image_limit) pixel
|
2022-10-14 13:29:33 +00:00
|
|
|
if values['media_mime'].startswith('image'):
|
|
|
|
new_image = cls.resize_image_file(values['media'])
|
|
|
|
if new_image is not None:
|
|
|
|
values['media'] = new_image
|
2023-06-06 14:46:03 +00:00
|
|
|
values['media_mime'] = cls._identify_file(
|
|
|
|
values['media'][:1024])
|
2022-10-14 13:29:33 +00:00
|
|
|
|
|
|
|
values['media_size'] = len(values['media'])
|
|
|
|
file_ext = mimetypes.guess_extension(values['media_mime'])
|
|
|
|
if 'media_name' in values.keys():
|
|
|
|
if not values['media_name'].endswith(file_ext):
|
|
|
|
# cut extension
|
|
|
|
if values['media_name'][-4] == '.':
|
|
|
|
values['media_name'] = values['media_name'][:-4]
|
|
|
|
values['media_name'] = values['media_name'] + file_ext
|
|
|
|
return values
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def validate(cls, lines):
|
|
|
|
""" deny invalid mime-types, file-sizes etc.
|
|
|
|
"""
|
|
|
|
super(Line, cls).validate(lines)
|
|
|
|
|
|
|
|
for line in lines:
|
|
|
|
if line.media is not None:
|
|
|
|
if line.media_size > 1024*1024*5:
|
|
|
|
raise UserError(gettext(
|
|
|
|
'cashbook_media.msg_file_too_big',
|
2023-06-06 14:46:03 +00:00
|
|
|
recname=line.rec_name))
|
|
|
|
if line.media_mime not in [
|
|
|
|
'application/pdf',
|
|
|
|
'image/png', 'image/jpg', 'image/jpeg']:
|
2022-10-14 13:29:33 +00:00
|
|
|
raise UserError(gettext(
|
|
|
|
'cashbook_media.msg_file_invalid_mime',
|
2023-06-06 14:46:03 +00:00
|
|
|
recname=line.rec_name,
|
|
|
|
fmime=line.media_mime))
|
2022-10-14 13:29:33 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create(cls, vlist):
|
|
|
|
""" add media-info
|
|
|
|
"""
|
|
|
|
vlist = [x.copy() for x in vlist]
|
|
|
|
for values in vlist:
|
|
|
|
if 'media' in values.keys():
|
|
|
|
values.update(cls.get_media_info(values))
|
|
|
|
return super(Line, cls).create(vlist)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def write(cls, *args):
|
|
|
|
""" update media-info
|
|
|
|
"""
|
|
|
|
actions = iter(args)
|
|
|
|
to_write = []
|
|
|
|
for records, values in zip(actions, actions):
|
|
|
|
if 'media' in values.keys():
|
|
|
|
values.update(cls.get_media_info(values))
|
|
|
|
|
|
|
|
to_write.extend([
|
|
|
|
records,
|
|
|
|
values,
|
|
|
|
])
|
|
|
|
super(Line, cls).write(*to_write)
|
|
|
|
|
|
|
|
# end Line
|