[MERGE] Merged with branch holding user image

bzr revid: tde@openerp.com-20120327070733-8hfaskwoi7lh6mhi
This commit is contained in:
Thibault Delavallée 2012-03-27 09:07:33 +02:00
commit 4e815d28cc
3 changed files with 39 additions and 40 deletions

View File

@ -2,8 +2,8 @@ User avatar
=========== ===========
This revision adds an avatar for users. This replaces the use of gravatar to emulate avatars, used in views like the tasks kanban view. Two fields have been added to the res.users model: This revision adds an avatar for users. This replaces the use of gravatar to emulate avatars, used in views like the tasks kanban view. Two fields have been added to the res.users model:
- avatar, a binary field holding the image - avatar_big, a binary field holding the image. It is base-64 encoded, and PIL-supported. Images stored are resized to 540x450 px, to limitate the binary field size.
- avatar_mini, a binary field holding an automatically resized version of the avatar. Dimensions of the resized avatar are 180x150. - avatar, a function binary field holding an automatically resized version of the avatar_big field. It is also base-64 encoded, and PIL-supported. Dimensions of the resized avatar are 180x150. This field is used as an inteface to get and set the user avatar.
User avatar has to be used everywhere an image depicting users is likely to be used, by using the avatar_mini field. When changing the avatar through the avatar function field, the new image is automatically resized to 540x450, and stored in the avatar_big field. This triggers the function field, that will compute a 180x150 resized version of the image.
An avatar field has been added to the users form view, as well as in Preferences. When creating a new user, a default avatar is chosen among 6 possible default images. An avatar field has been added to the users form view, as well as in Preferences. When creating a new user, a default avatar is chosen among 6 possible default images.

View File

@ -94,7 +94,7 @@
</group> </group>
<group col="2" colspan="1"> <group col="2" colspan="1">
<separator string="Avatar" colspan="2"/> <separator string="Avatar" colspan="2"/>
<field name="avatar" widget='image' nolabel="1" colspan="2"/> <field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)"/>
</group> </group>
</group> </group>
<group name="default_filters" colspan="2" col="2"> <group name="default_filters" colspan="2" col="2">
@ -130,7 +130,7 @@
<group colspan="7" col="7"> <group colspan="7" col="7">
<group col="2" colspan="1" name="avatar"> <group col="2" colspan="1" name="avatar">
<separator string="Avatar" colspan="2"/> <separator string="Avatar" colspan="2"/>
<field name="avatar_mini" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar_mini(avatar_mini)"/> <field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)"/>
</group> </group>
<group col="3" colspan="2" name="preferences"> <group col="3" colspan="2" name="preferences">
<separator string="Preferences" colspan="3"/> <separator string="Preferences" colspan="3"/>

View File

@ -25,24 +25,20 @@ from functools import partial
import pytz import pytz
import netsvc import io, StringIO
import pooler
import tools
from osv import fields,osv
from osv.orm import browse_record
from service import security
from tools.translate import _
import openerp
import openerp.exceptions
from lxml import etree from lxml import etree
from lxml.builder import E from lxml.builder import E
import netsvc
import openerp
# for avatar resizing import openerp.exceptions
import io, StringIO from osv import fields,osv
from osv.orm import browse_record
from PIL import Image from PIL import Image
# for default avatar choice import pooler
import random import random
from service import security
import tools
from tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -220,27 +216,31 @@ class users(osv.osv):
extended_users = group_obj.read(cr, uid, extended_group_id, ['users'], context=context)['users'] extended_users = group_obj.read(cr, uid, extended_group_id, ['users'], context=context)['users']
return dict(zip(ids, ['extended' if user in extended_users else 'simple' for user in ids])) return dict(zip(ids, ['extended' if user in extended_users else 'simple' for user in ids]))
def onchange_avatar_mini(self, cr, uid, ids, value, context=None): def onchange_avatar(self, cr, uid, ids, value, context=None):
return {'value': {'avatar': value, 'avatar_mini': self._avatar_resize(cr, uid, value) } } 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_mini(self, cr, uid, id, name, value, args, context=None): def _set_avatar(self, cr, uid, id, name, value, args, context=None):
return self.write(cr, uid, [id], {'avatar': value}, context=context) 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, context=None): def _avatar_resize(self, cr, uid, avatar, height=180, width=150, context=None):
image_stream = io.BytesIO(avatar.decode('base64')) image_stream = io.BytesIO(avatar.decode('base64'))
img = Image.open(image_stream) img = Image.open(image_stream)
img.thumbnail((180, 150), Image.ANTIALIAS) img.thumbnail((height, width), Image.ANTIALIAS)
img_stream = StringIO.StringIO() img_stream = StringIO.StringIO()
img.save(img_stream, "JPEG") img.save(img_stream, "JPEG")
return img_stream.getvalue().encode('base64') return img_stream.getvalue().encode('base64')
def _get_avatar_mini(self, cr, uid, ids, name, args, context=None): def _get_avatar(self, cr, uid, ids, name, args, context=None):
result = {} result = dict.fromkeys(ids, False)
for user in self.browse(cr, uid, ids, context=context): for user in self.browse(cr, uid, ids, context=context):
if not user.avatar: if user.avatar_big:
result[user.id] = False result[user.id] = self._avatar_resize(cr, uid, user.avatar_big, context=context)
else:
result[user.id] = self._avatar_resize(cr, uid, user.avatar)
return result return result
def _set_new_password(self, cr, uid, id, name, value, args, context=None): def _set_new_password(self, cr, uid, id, name, value, args, context=None):
@ -272,11 +272,11 @@ class users(osv.osv):
"otherwise leave empty. After a change of password, the user has to login again."), "otherwise leave empty. After a change of password, the user has to login again."),
'user_email': fields.char('Email', size=64), 'user_email': fields.char('Email', size=64),
'signature': fields.text('Signature', size=64), 'signature': fields.text('Signature', size=64),
'avatar': fields.binary('User Avatar'), '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_mini': fields.function(_get_avatar_mini, fnct_inv=_set_avatar_mini, string='User Avatar Mini', type="binary", 'avatar': fields.function(_get_avatar, fnct_inv=_set_avatar, string='Avatar', type="binary",
store = { store = {
'res.users': (lambda self, cr, uid, ids, c={}: ids, ['avatar'], 10), '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."),
'active': fields.boolean('Active'), '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."), '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."), 'menu_id': fields.many2one('ir.actions.actions', 'Menu Action', help="If specified, the action will replace the standard menu for this user."),
@ -389,15 +389,14 @@ class users(osv.osv):
return result return result
def _get_avatar(self, cr, uid, context=None): def _get_avatar(self, cr, uid, context=None):
# default avatar file name: avatar0 -> avatar6, choose randomly # default avatar file name: avatar0 -> avatar6.jpg, choose randomly
random.seed()
avatar_path = openerp.modules.get_module_resource('base', 'images', 'avatar%d.jpg' % random.randint(0, 6)) avatar_path = openerp.modules.get_module_resource('base', 'images', 'avatar%d.jpg' % random.randint(0, 6))
return self._avatar_resize(cr, uid, open(avatar_path, 'rb').read().encode('base64')) return self._avatar_resize(cr, uid, open(avatar_path, 'rb').read().encode('base64'), context=context)
_defaults = { _defaults = {
'password' : '', 'password' : '',
'context_lang': 'en_US', 'context_lang': 'en_US',
'avatar_mini': _get_avatar, 'avatar': _get_avatar,
'active' : True, 'active' : True,
'menu_id': _get_menu, 'menu_id': _get_menu,
'company_id': _get_company, 'company_id': _get_company,