[IMP] res_partner: moved the avatar colorize part of the image code to tools/image.py, where it should have been placed at revs 4373-4375.

bzr revid: tde@openerp.com-20120907092206-1pgbcvt8tn23m114
This commit is contained in:
Thibault Delavallée 2012-09-07 11:22:06 +02:00
parent 8c0cf0ae9a
commit 171533d8dc
3 changed files with 69 additions and 55 deletions

View File

@ -21,7 +21,6 @@
import math import math
import openerp import openerp
import os
from osv import osv, fields from osv import osv, fields
import re import re
import tools import tools
@ -58,20 +57,20 @@ class res_partner_category(osv.osv):
return super(res_partner_category, self).name_get(cr, uid, ids, context=context) return super(res_partner_category, self).name_get(cr, uid, ids, context=context)
if isinstance(ids, (int, long)): if isinstance(ids, (int, long)):
ids = [ids] ids = [ids]
reads = self.read(cr, uid, ids, ['name','parent_id'], context=context) reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
res = [] res = []
for record in reads: for record in reads:
name = record['name'] name = record['name']
if record['parent_id']: if record['parent_id']:
name = record['parent_id'][1]+' / '+name name = record['parent_id'][1] + ' / ' + name
res.append((record['id'], name)) res.append((record['id'], name))
return res return res
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
if not args: if not args:
args=[] args = []
if not context: if not context:
context={} context = {}
if name: if name:
# Be sure name_search is symetric to name_get # Be sure name_search is symetric to name_get
name = name.split(' / ')[-1] name = name.split(' / ')[-1]
@ -85,23 +84,23 @@ class res_partner_category(osv.osv):
res = self.name_get(cr, uid, ids, context=context) res = self.name_get(cr, uid, ids, context=context)
return dict(res) return dict(res)
_description='Partner Categories' _description = 'Partner Categories'
_name = 'res.partner.category' _name = 'res.partner.category'
_columns = { _columns = {
'name': fields.char('Category Name', required=True, size=64, translate=True), 'name': fields.char('Category Name', required=True, size=64, translate=True),
'parent_id': fields.many2one('res.partner.category', 'Parent Category', select=True, ondelete='cascade'), 'parent_id': fields.many2one('res.partner.category', 'Parent Category', select=True, ondelete='cascade'),
'complete_name': fields.function(_name_get_fnc, type="char", string='Full Name'), 'complete_name': fields.function(_name_get_fnc, type="char", string='Full Name'),
'child_ids': fields.one2many('res.partner.category', 'parent_id', 'Child Categories'), 'child_ids': fields.one2many('res.partner.category', 'parent_id', 'Child Categories'),
'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."), 'active': fields.boolean('Active', help="The active field allows you to hide the category without removing it."),
'parent_left' : fields.integer('Left parent', select=True), 'parent_left': fields.integer('Left parent', select=True),
'parent_right' : fields.integer('Right parent', select=True), 'parent_right': fields.integer('Right parent', select=True),
'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'), 'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'),
} }
_constraints = [ _constraints = [
(osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id']) (osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
] ]
_defaults = { _defaults = {
'active' : lambda *a: 1, 'active': lambda *a: 1,
} }
_parent_store = True _parent_store = True
_parent_order = 'name' _parent_order = 'name'
@ -113,7 +112,7 @@ class res_partner_title(osv.osv):
_columns = { _columns = {
'name': fields.char('Title', required=True, size=46, translate=True), 'name': fields.char('Title', required=True, size=46, translate=True),
'shortcut': fields.char('Abbreviation', size=16, translate=True), 'shortcut': fields.char('Abbreviation', size=16, translate=True),
'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24) 'domain': fields.selection([('partner', 'Partner'), ('contact', 'Contact')], 'Domain', required=True, size=24)
} }
_defaults = { _defaults = {
'domain': 'contact', 'domain': 'contact',
@ -129,13 +128,13 @@ POSTAL_ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'countr
ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang') ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang')
class res_partner(osv.osv): class res_partner(osv.osv):
_description='Partner' _description = 'Partner'
_name = "res.partner" _name = "res.partner"
def _address_display(self, cr, uid, ids, name, args, context=None): def _address_display(self, cr, uid, ids, name, args, context=None):
res={} res = {}
for partner in self.browse(cr, uid, ids, context=context): for partner in self.browse(cr, uid, ids, context=context):
res[partner.id] =self._display_address(cr, uid, partner, context=context) res[partner.id] = self._display_address(cr, uid, partner, context=context)
return res return res
def _get_image(self, cr, uid, ids, name, args, context=None): def _get_image(self, cr, uid, ids, name, args, context=None):
@ -143,7 +142,7 @@ class res_partner(osv.osv):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = tools.image_get_resized_images(obj.image) result[obj.id] = tools.image_get_resized_images(obj.image)
return result return result
def _set_image(self, cr, uid, id, name, value, args, context=None): def _set_image(self, cr, uid, id, name, value, args, context=None):
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
@ -151,7 +150,7 @@ class res_partner(osv.osv):
_columns = { _columns = {
'name': fields.char('Name', size=128, required=True, select=True), 'name': fields.char('Name', size=128, required=True, select=True),
'date': fields.date('Date', select=1), 'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title','Title'), 'title': fields.many2one('res.partner.title', 'Title'),
'parent_id': fields.many2one('res.partner', 'Owned by'), 'parent_id': fields.many2one('res.partner', 'Owned by'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'), 'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'),
'ref': fields.char('Reference', size=64, select=1), 'ref': fields.char('Reference', size=64, select=1),
@ -162,9 +161,9 @@ class res_partner(osv.osv):
"It is important to set a value for this field. You should use the same timezone " "It is important to set a value for this field. You should use the same timezone "
"that is otherwise used to pick and render date and time values: your computer's timezone."), "that is otherwise used to pick and render date and time values: your computer's timezone."),
'user_id': fields.many2one('res.users', 'Salesperson', help='The internal user that is in charge of communicating with this partner if any.'), 'user_id': fields.many2one('res.users', 'Salesperson', help='The internal user that is in charge of communicating with this partner if any.'),
'vat': fields.char('TIN',size=32 ,help="Tax Identification Number. Check the box if the partner is subjected to taxes. Used by the some of the legal statements."), 'vat': fields.char('TIN', size=32, help="Tax Identification Number. Check the box if the partner is subjected to taxes. Used by the some of the legal statements."),
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'), 'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
'website': fields.char('Website',size=64, help="Website of Partner or Company"), 'website': fields.char('Website', size=64, help="Website of Partner or Company"),
'comment': fields.text('Notes'), 'comment': fields.text('Notes'),
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility 'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility
'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'), 'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'),
@ -175,8 +174,8 @@ class res_partner(osv.osv):
'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."), 'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."), 'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."),
'function': fields.char('Job Position', size=128), 'function': fields.char('Job Position', size=128),
'type': fields.selection( [('default','Default'), ('invoice','Invoice'), 'type': fields.selection([('default', 'Default'), ('invoice', 'Invoice'),
('delivery','Delivery'), ('contact','Contact'), ('delivery', 'Delivery'), ('contact', 'Contact'),
('other', 'Other')], 'Address Type', ('other', 'Other')], 'Address Type',
help="Used to select automatically the right address according to the context in sales and purchases documents."), help="Used to select automatically the right address according to the context in sales and purchases documents."),
'street': fields.char('Street', size=128), 'street': fields.char('Street', size=128),
@ -193,13 +192,12 @@ class res_partner(osv.osv):
'birthdate': fields.char('Birthdate', size=64), 'birthdate': fields.char('Birthdate', size=64),
'is_company': fields.boolean('Company', help="Check if the contact is a company, otherwise it is a person"), 'is_company': fields.boolean('Company', help="Check if the contact is a company, otherwise it is a person"),
'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"), 'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"),
# image: all image fields are base64 encoded and PIL-supported
'image': fields.binary("Image", 'image': fields.binary("Image",
help="This field holds the image used as avatar for the "\ help="This field holds the image used as avatar for the partner, limited to 1024x1024px"),
"partner. The image is base64 encoded, and PIL-supported. "\
"It is limited to a 1024x1024 px image."),
'image_medium': fields.function(_get_image, fnct_inv=_set_image, 'image_medium': fields.function(_get_image, fnct_inv=_set_image,
string="Medium-sized image", type="binary", multi="_get_image", string="Medium-sized image", type="binary", multi="_get_image",
store = { store={
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), 'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
}, },
help="Medium-sized image of the partner. It is automatically "\ help="Medium-sized image of the partner. It is automatically "\
@ -207,7 +205,7 @@ class res_partner(osv.osv):
"Use this field in form views or some kanban views."), "Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image, 'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Small-sized image", type="binary", multi="_get_image", string="Small-sized image", type="binary", multi="_get_image",
store = { store={
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), 'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
}, },
help="Small-sized image of the partner. It is automatically "\ help="Small-sized image of the partner. It is automatically "\
@ -230,40 +228,28 @@ class res_partner(osv.osv):
if is_company: if is_company:
image = open(openerp.modules.get_module_resource('base', 'static/src/img', 'company_image.png')).read() image = open(openerp.modules.get_module_resource('base', 'static/src/img', 'company_image.png')).read()
else: else:
from PIL import Image image = tools.image_colorize(open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png')).read())
from StringIO import StringIO return tools.image_resize_image_big(image.encode('base64'))
color = (255,255,255)
if colorize:
from random import random
color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32))
face = Image.open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png'))
avatar = Image.new('RGB', face.size)
avatar.paste(color)
avatar.paste(face, mask=face)
buffer = StringIO()
avatar.save(buffer, 'PNG')
image = buffer.getvalue()
return image.encode('base64')
_defaults = { _defaults = {
'active': True, 'active': True,
'lang': lambda self, cr, uid, context: context.get('lang', 'en_US'), 'lang': lambda self, cr, uid, ctx: ctx.get('lang', 'en_US'),
'tz': lambda self, cr, uid, context: context.get('tz', False), 'tz': lambda self, cr, uid, ctx: ctx.get('tz', False),
'customer': True, 'customer': True,
'category_id': _default_category, 'category_id': _default_category,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c), 'company_id': lambda self, cr, uid, ctx: self.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=ctx),
'color': 0, 'color': 0,
'is_company': False, 'is_company': False,
'type': 'default', 'type': 'default',
'use_parent_address': True, 'use_parent_address': True,
'image': lambda self, cr, uid, context: self._get_default_image(cr, uid, context.get('default_is_company', False), context), 'image': lambda self, cr, uid, ctx: self._get_default_image(cr, uid, ctx.get('default_is_company', False), ctx),
} }
def copy(self, cr, uid, id, default=None, context=None): def copy(self, cr, uid, id, default=None, context=None):
if default is None: if default is None:
default = {} default = {}
name = self.read(cr, uid, [id], ['name'], context)[0]['name'] name = self.read(cr, uid, [id], ['name'], context)[0]['name']
default.update({'name': _('%s (copy)')%(name)}) default.update({'name': _('%s (copy)') % (name)})
return super(res_partner, self).copy(cr, uid, id, default, context) return super(res_partner, self).copy(cr, uid, id, default, context)
def onchange_type(self, cr, uid, ids, is_company, context=None): def onchange_type(self, cr, uid, ids, is_company, context=None):

View File

@ -244,14 +244,14 @@ class res_users(osv.osv):
return result return result
_defaults = { _defaults = {
'password' : '', 'password': '',
'active' : True, 'active': True,
'customer': False, 'customer': False,
'menu_id': _get_menu, 'menu_id': _get_menu,
'company_id': _get_company, 'company_id': _get_company,
'company_ids': _get_companies, 'company_ids': _get_companies,
'groups_id': _get_group, 'groups_id': _get_group,
'image': lambda self, cr, uid, context: self.pool.get('res.partner')._get_default_image(cr, uid, False, context, colorize=True), 'image': lambda self, cr, uid, ctx={}: self.pool.get('res.partner')._get_default_image(cr, uid, False, ctx, colorize=True),
} }
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):

View File

@ -20,9 +20,11 @@
############################################################################## ##############################################################################
import io import io
import StringIO
from PIL import Image from PIL import Image
from PIL import ImageFilter from PIL import ImageFilter
import StringIO from random import random
# ---------------------------------------- # ----------------------------------------
# Image resizing # Image resizing
@ -48,7 +50,7 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
image. image.
- past the thumbnail on the transparent background and center - past the thumbnail on the transparent background and center
it. it.
:param base64_source: base64-encoded version of the source :param base64_source: base64-encoded version of the source
image image
:param size: tuple(height, width) :param size: tuple(height, width)
@ -93,7 +95,7 @@ def image_resize_image_medium(base64_source, size=(180, 180), encoding='base64',
if not base64_source: if not base64_source:
return False return False
return image_resize_image(base64_source, size, encoding, filetype) return image_resize_image(base64_source, size, encoding, filetype)
def image_resize_image_small(base64_source, size=(50, 50), encoding='base64', filetype='PNG'): def image_resize_image_small(base64_source, size=(50, 50), encoding='base64', filetype='PNG'):
""" Wrapper on image_resize_image, to resize to the standard 'small' image """ Wrapper on image_resize_image, to resize to the standard 'small' image
size: 50x50. size: 50x50.
@ -104,6 +106,29 @@ def image_resize_image_small(base64_source, size=(50, 50), encoding='base64', fi
return False return False
return image_resize_image(base64_source, size, encoding, filetype) return image_resize_image(base64_source, size, encoding, filetype)
# ----------------------------------------
# Colors
# ---------------------------------------
def image_colorize(original, randomize=True, color=(255, 255, 255)):
""" Add a color to the transparent background of an image.
:param original: file object on the original image file
:param randomize: randomize the background color
:param color: background-color, if not randomize
"""
# create a new image, based on the original one
original = Image.open(io.BytesIO(original))
image = Image.new('RGB', original.size)
# generate the background color, past it as background
if randomize:
color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32))
image.paste(color)
image.paste(original, mask=original)
# return the new image
buffer = StringIO.StringIO()
image.save(buffer, 'PNG')
return buffer.getvalue()
# ---------------------------------------- # ----------------------------------------
# Misc image tools # Misc image tools
# --------------------------------------- # ---------------------------------------
@ -118,7 +143,7 @@ def image_get_resized_images(base64_source, return_big=False, return_medium=True
Default parameters are given to be used for the getter of functional Default parameters are given to be used for the getter of functional
image fields, for example with res.users or res.partner. It returns image fields, for example with res.users or res.partner. It returns
only image_medium and image_small values, to update those fields. only image_medium and image_small values, to update those fields.
:param base64_source: if set to False, other values are set to False :param base64_source: if set to False, other values are set to False
also. The purpose is to be linked to the fields that hold images in also. The purpose is to be linked to the fields that hold images in
OpenERP and that are binary fields. OpenERP and that are binary fields.
@ -135,7 +160,10 @@ def image_get_resized_images(base64_source, return_big=False, return_medium=True
previous parameters. previous parameters.
""" """
return_dict = dict() return_dict = dict()
if return_big: return_dict[big_name] = image_resize_image_big(base64_source) if return_big:
if return_medium: return_dict[medium_name] = image_resize_image_medium(base64_source) return_dict[big_name] = image_resize_image_big(base64_source)
if return_small: return_dict[small_name] = image_resize_image_small(base64_source) if return_medium:
return return_dict return_dict[medium_name] = image_resize_image_medium(base64_source)
if return_small:
return_dict[small_name] = image_resize_image_small(base64_source)
return return_dict