From 403ca77b8f1afd265f18bb49a6de7412da65d7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 27 Jun 2012 12:12:56 +0200 Subject: [PATCH 01/16] [ADD] tools: added image.py, intended to hold the resize functions; also added in__init__. bzr revid: tde@openerp.com-20120627101256-4q4miayorfcqh05w --- openerp/tools/__init__.py | 21 +++++++++++---------- openerp/tools/image.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 openerp/tools/image.py diff --git a/openerp/tools/__init__.py b/openerp/tools/__init__.py index 6875ebd78fc..76825060f31 100644 --- a/openerp/tools/__init__.py +++ b/openerp/tools/__init__.py @@ -19,19 +19,20 @@ # ############################################################################## -import copy -import win32 -from config import config -from misc import * -from convert import * -from translate import * -from graph import graph from amount_to_text import * from amount_to_text_en import * -from pdf_utils import * -from yaml_import import * -from sql import * +from config import config +from convert import * +import copy from float_utils import * +from graph import graph +from image import * +from misc import * +from pdf_utils import * +from sql import * +from translate import * +from yaml_import import * +import win32 #.apidoc title: Tools diff --git a/openerp/tools/image.py b/openerp/tools/image.py new file mode 100644 index 00000000000..4dc92704c9f --- /dev/null +++ b/openerp/tools/image.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP s.a. (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## From 59ec75cbb0f1bc3385742134c03a85a111960ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 27 Jun 2012 15:27:39 +0200 Subject: [PATCH 02/16] [ADD] tools.images: added function to resize images. bzr revid: tde@openerp.com-20120627132739-zdgvdqh2nbsfkyv7 --- openerp/tools/image.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/openerp/tools/image.py b/openerp/tools/image.py index 4dc92704c9f..edc251e40f6 100644 --- a/openerp/tools/image.py +++ b/openerp/tools/image.py @@ -18,3 +18,33 @@ # along with this program. If not, see . # ############################################################################## + +import io +from PIL import Image +import StringIO + +def resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False): + image_stream = io.BytesIO(base64_source.decode(encoding)) + image = Image.open(image_stream) + # check image size: do not create a thumbnail if avoiding smaller images + if avoid_if_small and image.size[0] <= size[0] and image.size[1] <= size[1]: + return base64_source + # create a thumbnail: will resize and keep ratios + image.thumbnail(size, Image.ANTIALIAS) + # create a transparent image for background + background = Image.new('RGBA', size, (255, 255, 255, 0)) + # past the resized image on the background + background.paste(image, ((size[0] - image.size[0]) / 2, (size[1] - image.size[1]) / 2)) + # return an encoded image + background_stream = StringIO.StringIO() + background.save(background_stream, filetype) + return background_stream.getvalue().encode(encoding) + +def resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG'): + return resize_image(base64_source, size, encoding, filetype, True) + +def resize_image_medium(base64_source, size=(180, 180), encoding='base64', filetype='PNG'): + return resize_image(base64_source, size, encoding, filetype) + +def resize_image_small(base64_source, size=(50, 50), encoding='base64', filetype='PNG'): + return resize_image(base64_source, size, encoding, filetype) From da294291cf00955581114848f38b244b98567492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 27 Jun 2012 15:28:07 +0200 Subject: [PATCH 03/16] [IMP] res.users: from avatar to image: iamge, image_medium and image_small, using tools to resize. bzr revid: tde@openerp.com-20120627132807-crf2vi23ato69v47 --- openerp/addons/base/res/res_users.py | 93 +++++++++++++--------- openerp/addons/base/res/res_users_view.xml | 4 +- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index e22b5addf48..969f455cef4 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -152,33 +152,6 @@ class users(osv.osv): body=(self.get_welcome_mail_body(cr, uid, context=context) % user)) return ir_mail_server.send_email(cr, uid, msg, context=context) - def onchange_avatar(self, cr, uid, ids, value, context=None): - if not value: - return {'value': {'avatar_big': value, 'avatar': value} } - return {'value': {'avatar_big': self._avatar_resize(cr, uid, value, 540, 450, context=context), 'avatar': self._avatar_resize(cr, uid, value, context=context)} } - - def _set_avatar(self, cr, uid, id, name, value, args, context=None): - if not value: - vals = {'avatar_big': value} - else: - vals = {'avatar_big': self._avatar_resize(cr, uid, value, 540, 450, context=context)} - return self.write(cr, uid, [id], vals, context=context) - - def _avatar_resize(self, cr, uid, avatar, height=180, width=150, context=None): - image_stream = io.BytesIO(avatar.decode('base64')) - img = Image.open(image_stream) - img.thumbnail((height, width), Image.ANTIALIAS) - img_stream = StringIO.StringIO() - img.save(img_stream, "PNG") - return img_stream.getvalue().encode('base64') - - def _get_avatar(self, cr, uid, ids, name, args, context=None): - result = dict.fromkeys(ids, False) - for user in self.browse(cr, uid, ids, context=context): - if user.avatar_big: - result[user.id] = self._avatar_resize(cr, uid, user.avatar_big, context=context) - return result - def _set_new_password(self, cr, uid, id, name, value, args, context=None): if value is False: # Do not update the password if no value is provided, ignore silently. @@ -194,6 +167,35 @@ class users(osv.osv): def _get_password(self, cr, uid, ids, arg, karg, context=None): return dict.fromkeys(ids, '') + def _get_image_resized(self, cr, uid, ids, name, args, context=None): + result = dict.fromkeys(ids, False) + for user in self.browse(cr, uid, ids, context=context): + result[user.id] = {'image_medium': False, 'image_small': False} + if user.image: + result[user.id]['image_medium'] = tools.resize_image_medium(user.image) + result[user.id]['image_small'] = tools.resize_image_small(user.image) + return result + + def _set_image_resized(self, cr, uid, id, name, value, args, context=None): + if not value: + vals = {'image': value} + else: + vals = {'image': tools.resize_image_big(value)} + return self.write(cr, uid, [id], vals, context=context) + + def onchange_image(self, cr, uid, ids, value, context=None): + if not value: + return {'value': { + 'image': value, + 'image_medium': value, + 'image_small': value, + }} + return {'value': { + 'image': tools.resize_image_big(value), + 'image_medium': tools.resize_image_medium(value), + 'image_small': tools.resize_image_small(value), + }} + _columns = { 'id': fields.integer('ID'), 'name': fields.char('User Name', size=64, required=True, select=True, @@ -208,11 +210,28 @@ class users(osv.osv): "otherwise leave empty. After a change of password, the user has to login again."), 'user_email': fields.char('Email', size=64), 'signature': fields.text('Signature', size=64), - 'avatar_big': fields.binary('Big-sized avatar', help="This field holds the image used as avatar for the user. The avatar field is used as an interface to access this field. The image is base64 encoded, and PIL-supported. It is stored as a 540x450 px image, in case a bigger image must be used."), - 'avatar': fields.function(_get_avatar, fnct_inv=_set_avatar, string='Avatar', type="binary", + 'image': fields.binary("Avatar", + help="This field holds the image used as avatar for the "\ + "user. The avatar field is used as an interface to "\ + "access this field. The image is base64 encoded, "\ + "and PIL-supported. It is stored as a 540x450 px "\ + "image, in case a bigger image must be used."), + 'image_medium': fields.function(_get_image_resized, fnct_inv=_set_image_resized, + string="Medium-sized avatar", type="binary", multi="_get_image_resized", store = { - 'res.users': (lambda self, cr, uid, ids, c={}: ids, ['avatar_big'], 10), - }, help="Image used as avatar for the user. It is automatically resized as a 180x150 px image. This field serves as an interface to the avatar_big field."), + 'res.users': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), + }, + help="Medium-sized image of the user. It is automatically "\ + "resized as a 180x180px image, with aspect ratio keps. "\ + "Use this field in form views or some kanban views."), + 'image_small': fields.function(_get_image_resized, fnct_inv=_set_image_resized, + string="Smal-sized avatar", type="binary", multi="_get_image_resized", + store = { + 'res.users': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), + }, + help="Small-sized image of the user. It is automatically "\ + "resized as a 50x50px image, with aspect ratio keps. "\ + "Use this field in form views or some kanban views."), 'active': fields.boolean('Active'), 'action_id': fields.many2one('ir.actions.actions', 'Home Action', help="If specified, this action will be opened at logon for this user, in addition to the standard menu."), 'menu_id': fields.many2one('ir.actions.actions', 'Menu Action', help="If specified, the action will replace the standard menu for this user."), @@ -321,16 +340,16 @@ class users(osv.osv): pass return result - def _get_avatar(self, cr, uid, context=None): - # default avatar file name: avatar0 -> avatar6.png, choose randomly - avatar_path = openerp.modules.get_module_resource('base', 'static/src/img', 'avatar%d.png' % random.randint(0, 6)) - return self._avatar_resize(cr, uid, open(avatar_path, 'rb').read().encode('base64'), context=context) + def _get_image(self, cr, uid, context=None): + # default image file name: avatar0 -> avatar6.png, choose randomly + image_path = openerp.modules.get_module_resource('base', 'static/src/img', 'avatar%d.png' % random.randint(0, 6)) + return tools.resize_image_big(open(image_path, 'rb').read().encode('base64')) _defaults = { 'password' : '', 'context_lang': lambda self, cr, uid, context: context.get('lang', 'en_US'), 'context_tz': lambda self, cr, uid, context: context.get('tz', False), - 'avatar': _get_avatar, + 'image': _get_image, 'active' : True, 'menu_id': _get_menu, 'company_id': _get_company, @@ -340,7 +359,7 @@ class users(osv.osv): } # User can write to a few of her own fields (but not her groups for example) - SELF_WRITEABLE_FIELDS = ['menu_tips','password', 'signature', 'action_id', 'company_id', 'user_email', 'name', 'avatar', 'avatar_big'] + SELF_WRITEABLE_FIELDS = ['menu_tips','password', 'signature', 'action_id', 'company_id', 'user_email', 'name', 'image', 'image_medium', 'image_small'] def write(self, cr, uid, ids, values, context=None): if not hasattr(ids, '__iter__'): diff --git a/openerp/addons/base/res/res_users_view.xml b/openerp/addons/base/res/res_users_view.xml index 82093dadd4b..28790a342a2 100644 --- a/openerp/addons/base/res/res_users_view.xml +++ b/openerp/addons/base/res/res_users_view.xml @@ -82,7 +82,7 @@
- +