bzr revid: hmo@tinyerp.com-20120809130651-rrje118wjbdur5i6
This commit is contained in:
Harry (OpenERP) 2012-08-09 18:36:51 +05:30
commit 52d7bb3256
16 changed files with 345 additions and 103 deletions

View File

@ -58,6 +58,7 @@
'module/wizard/base_module_configuration_view.xml',
'module/wizard/base_export_language_view.xml',
'module/wizard/base_update_translations_view.xml',
'module/wizard/base_module_immediate_install.xml',
'res/res_company_view.xml',
'res/res_request_view.xml',
'res/res_lang_view.xml',

View File

@ -355,22 +355,7 @@ class module(osv.osv):
:returns: next res.config item to execute
:rtype: dict[str, object]
"""
self.button_install(cr, uid, ids, context=context)
cr.commit()
_, pool = pooler.restart_pool(cr.dbname, update_module=True)
config = pool.get('res.config').next(cr, uid, [], context=context) or {}
if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'):
return config
# reload the client; open the first available root menu
menu_obj = self.pool.get('ir.ui.menu')
menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
return {
'type': 'ir.actions.client',
'tag': 'reload',
'params': {'menu_id': menu_ids and menu_ids[0] or False},
}
return self._button_immediate_function(cr, uid, ids, self.button_install, context=context)
def button_install_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
@ -415,8 +400,35 @@ class module(osv.osv):
known_dep_ids, exclude_states,context))
return list(known_dep_ids)
def _button_immediate_function(self, cr, uid, ids, function, context=None):
function(cr, uid, ids, context=context)
cr.commit()
_, pool = pooler.restart_pool(cr.dbname, update_module=True)
config = pool.get('res.config').next(cr, uid, [], context=context) or {}
if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'):
return config
# reload the client; open the first available root menu
menu_obj = self.pool.get('ir.ui.menu')
menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
return {
'type' : 'ir.actions.client',
'tag' : 'reload',
'params' : {'menu_id' : menu_ids and menu_ids[0] or False}
}
def button_immediate_uninstall(self, cr, uid, ids, context=None):
"""
Uninstall the selected module(s) immediately and fully,
returns the next res.config action to execute
"""
return self._button_immediate_function(cr, uid, ids, self.button_uninstall, context=context)
def button_uninstall(self, cr, uid, ids, context=None):
if any(m.name == 'base' for m in self.browse(cr, uid, ids)):
if any(m.name == 'base' for m in self.browse(cr, uid, ids, context=context)):
raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled"))
dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
@ -426,6 +438,13 @@ class module(osv.osv):
self.write(cr, uid, ids, {'state': 'installed'})
return True
def button_immediate_upgrade(self, cr, uid, ids, context=None):
"""
Upgrade the selected module(s) immediately and fully,
return the next res.config action to execute
"""
return self._button_immediate_function(cr, uid, ids, self.button_upgrade, context=context)
def button_upgrade(self, cr, uid, ids, context=None):
depobj = self.pool.get('ir.module.module.dependency')
todo = self.browse(cr, uid, ids, context=context)

View File

@ -117,8 +117,8 @@
<h1><field name="shortdesc"/></h1>
<h2 class="oe_fade"><field name="summary"/></h2>
<button name="button_immediate_install" states="uninstalled" string="Install" type="object" class="oe_highlight"/>
<button name="button_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/>
<button name="button_uninstall" states="installed" string="Uninstall" type="object"
<button name="button_immediate_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/>
<button name="button_immediate_uninstall" states="installed" string="Uninstall" type="object"
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" type="object"/>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="action_server_module_immediate_install" model="ir.actions.server">
<field name="name">Module Immediate Install</field>
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_ir_module_module" />
<field name="state">code</field>
<field name="code">self.button_immediate_install(cr, uid, context.get('active_ids', []), context=context)</field>
</record>
<record model="ir.values" id="action_module_immediate_install">
<field name="name">action_module_immediate_install</field>
<field name="action_id" ref="action_server_module_immediate_install" />
<field name="value" eval="'ir.actions.server,' + str(ref('action_server_module_immediate_install'))" />
<field name="key">action</field>
<field name="model_id" ref="model_ir_module_module" />
<field name="model">ir.module.module</field>
<field name="key2">client_action_multi</field>
</record>
</data>
</openerp>

View File

@ -139,7 +139,6 @@ class res_partner_bank(osv.osv):
'state': fields.selection(_bank_type_get, 'Bank Account Type', required=True,
change_default=True),
'sequence': fields.integer('Sequence'),
'footer': fields.boolean("Display on Reports", help="Display this bank account on the footer of printed documents like invoices and sales orders.")
}
_defaults = {
@ -212,7 +211,6 @@ class res_partner_bank(osv.osv):
if c.partner_id:
r = self.onchange_partner_id(cr, uid, ids, c.partner_id.id, context=context)
r['value']['partner_id'] = c.partner_id.id
r['value']['footer'] = 1
result = r
return result

View File

@ -97,7 +97,6 @@
<field name="state"/>
<field name="acc_number"/>
<field name="company_id" on_change="onchange_company_id(company_id)" invisible="context.get('company_hide', True)" widget="selection"/>
<field name="footer" attrs="{'invisible': [('company_id','=',False)]}"/>
</group>
<group>
<group name="owner" string="Bank Account Owner">
@ -134,7 +133,6 @@
<field name="acc_number"/>
<field name="bank_name"/>
<field name="company_id" invisible="context.get('company_hide', True)"/>
<field name="footer" invisible="context.get('company_hide', True)"/>
<field name="partner_id"/>
</tree>
</field>
@ -157,7 +155,7 @@
<field name="res_model">res.partner.bank</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help">Configure your company's bank accounts and select those that must appear on the report footer. You can reorder bank accounts from the list view. If you use the accounting application of OpenERP, journals and accounts will be created automatically based on these data.</field>
<field name="help">Configure your company's bank accounts. You can reorder bank accounts from the list view. If you use the accounting application of OpenERP, journals and accounts will be created automatically based on these data.</field>
</record>
<menuitem action="action_res_partner_bank_account_form"
id="menu_action_res_partner_bank_form"

View File

@ -114,7 +114,7 @@ class res_company(osv.osv):
'rml_header': fields.text('RML Header', required=True),
'rml_header2': fields.text('RML Internal Header', required=True),
'rml_header3': fields.text('RML Internal Header for Landscape Reports', required=True),
'logo': fields.related('partner_id', 'photo', string="Logo", type="binary"),
'logo': fields.related('partner_id', 'image', string="Logo", type="binary"),
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
'currency_ids': fields.one2many('res.currency', 'company_id', 'Currency'),
'user_ids': fields.many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', 'Accepted Users'),

View File

@ -133,6 +133,15 @@ class res_partner(osv.osv):
res[partner.id] =self._display_address(cr, uid, partner, context=context)
return res
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = tools.image_get_resized_images(obj.image)
return result
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)
_order = "name"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
@ -174,7 +183,26 @@ class res_partner(osv.osv):
'birthdate': fields.char('Birthdate', size=64),
'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"),
'photo': fields.binary('Photo'),
'image': fields.binary("Image",
help="This field holds the image used as avatar for the "\
"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,
string="Medium-sized image", type="binary", multi="_get_image",
store = {
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
},
help="Medium-sized image of the partner. It is automatically "\
"resized as a 180x180 px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Small-sized image", type="binary", multi="_get_image",
store = {
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
},
help="Small-sized image of the partner. It is automatically "\
"resized as a 50x50 px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required."),
'company_id': fields.many2one('res.company', 'Company', select=1),
'color': fields.integer('Color Index'),
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
@ -187,12 +215,12 @@ class res_partner(osv.osv):
return [context['category_id']]
return False
def _get_photo(self, cr, uid, is_company, context=None):
def _get_default_image(self, cr, uid, is_company, context=None):
if is_company:
path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'company_icon.png')
image_path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'company_icon.png')
else:
path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'photo.png')
return open(path, 'rb').read().encode('base64')
image_path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'photo.png')
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
_defaults = {
'active': True,
@ -203,7 +231,7 @@ class res_partner(osv.osv):
'is_company': False,
'type': 'default',
'use_parent_address': True,
'photo': lambda self, cr, uid, context: self._get_photo(cr, uid, False, context),
'image': lambda self, cr, uid, context: self._get_default_image(cr, uid, False, context),
}
def copy(self, cr, uid, id, default=None, context=None):
@ -214,8 +242,9 @@ class res_partner(osv.osv):
return super(res_partner, self).copy(cr, uid, id, default, context)
def onchange_type(self, cr, uid, ids, is_company, context=None):
value = {'title': False,
'photo': self._get_photo(cr, uid, is_company, context)}
# get value as for an onchange on the image
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True)
value['title'] = False
if is_company:
value['parent_id'] = False
domain = {'title': [('domain', '=', 'partner')]}
@ -280,8 +309,9 @@ class res_partner(osv.osv):
domain_siblings = [('parent_id', '=', vals['parent_id']), ('use_parent_address', '=', True)]
update_ids = [vals['parent_id']] + self.search(cr, uid, domain_siblings, context=context)
self.update_address(cr, uid, update_ids, vals, context)
if 'photo' not in vals :
vals['photo'] = self._get_photo(cr, uid, vals.get('is_company', False) or context.get('default_is_company'), context)
if 'image' not in vals :
image_value = self._get_default_image(cr, uid, vals.get('is_company', False) or context.get('default_is_company'), context)
vals.update(tools.image_get_resized_images(image_value, return_big=True))
return super(res_partner,self).create(cr, uid, vals, context=context)
def update_address(self, cr, uid, ids, vals, context=None):
@ -329,12 +359,13 @@ class res_partner(osv.osv):
email = contact_regex_res[0][1]
rec_id = self.create(cr, uid, {self._rec_name: name, 'email': email}, context);
return self.name_get(cr, uid, [rec_id], context)[0]
elif email_regex:
elif email_regex_res:
email = '%s' % (email_regex_res[0])
rec_id = self.create(cr, uid, {self._rec_name: email, 'email': email}, context);
return self.name_get(cr, uid, [rec_id], context)[0]
else:
return super(res_partner, self).create(cr, uid, name, context)
rec_id = super(res_partner, self).create(cr, uid, {self._rec_name: name}, context)
return self.name_get(cr, uid, [rec_id], context)[0]
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
if not args:

View File

@ -99,7 +99,7 @@
<field name="arch" type="xml">
<form string="Partners" version="7.0">
<sheet>
<field name="photo" widget='image' class="oe_avatar oe_left"/>
<field name="image_small" widget='image' class="oe_avatar oe_left"/>
<div class="oe_title">
<div class="oe_edit_only">
<label for="name"/> (
@ -168,7 +168,7 @@
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="photo"/>
<field name="image_small"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
@ -181,7 +181,7 @@
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<a type="edit">
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" class="oe_avatar oe_kanban_avatar_toto"/>
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.value)" class="oe_avatar oe_kanban_avatar_smallbox"/>
</a>
<div class="oe_module_desc">
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
@ -207,8 +207,24 @@
</t>
</templates>
</kanban>
<form string="Contact" version="7.0">
<field name="image_small" widget='image' class="oe_avatar oe_left"/>
<div class="oe_title">
<group>
<field name="name"/>
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
<field name="function" placeholder="e.g. Sales Director"/>
<field name="email"/>
<field name="phone"/>
<field name="mobile"/>
</group>
</div>
</form>
</field>
</page>
<page string="Internal Notes">
<field name="comment" placeholder="Internal notes about this customer..."/>
</page>
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<group>
<group>
@ -232,9 +248,6 @@
<!-- The History page becomes visible as soon as there is something to display inside -->
<page string="History" name="page_history" invisible="True">
</page>
<page string="Internal Notes">
<field name="comment" placeholder="Internal notes about this customer..."/>
</page>
</notebook>
</sheet>
</form>
@ -258,7 +271,7 @@
<filter string="Customers" name="customer" icon="terp-personal" domain="[('customer','=',1)]" help="Customer Partners"/>
<separator/>
<filter string="Suppliers" name="supplier" icon="terp-personal" domain="[('supplier','=',1)]" help="Supplier Partners"/>
<field name="category_id"/>
<field name="category_id" string="Category" filter_domain="[('category_id','ilike', self)]"/>
<field name="user_id"/>
<field name="parent_id" filter_domain="[('parent_id','child_of',[self])]"/>
<group expand="0" string="Group By...">
@ -286,7 +299,7 @@
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="photo"/>
<field name="image_small"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
@ -297,7 +310,7 @@
<t t-name="kanban-box">
<div class="oe_kanban_vignette">
<a type="edit">
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" class="oe_kanban_image"/>
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.value)" class="oe_kanban_image"/>
</a>
<div class="oe_kanban_details">
<h4 class="oe_partner_heading"><a type="edit"><field name="name"/></a></h4>

View File

@ -25,7 +25,6 @@ from functools import partial
import pytz
import io, StringIO
from lxml import etree
from lxml.builder import E
import netsvc
@ -33,7 +32,6 @@ import openerp
import openerp.exceptions
from osv import fields,osv
from osv.orm import browse_record
from PIL import Image
import pooler
import random
from service import security
@ -152,33 +150,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 +165,15 @@ class users(osv.osv):
def _get_password(self, cr, uid, ids, arg, karg, context=None):
return dict.fromkeys(ids, '')
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = tools.image_get_resized_images(obj.image)
return result
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)
_columns = {
'id': fields.integer('ID'),
'name': fields.char('User Name', size=64, required=True, select=True,
@ -208,11 +188,26 @@ 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 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,
string="Medium-sized avatar", type="binary", multi="_get_image",
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 180x180 px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Smal-sized avatar", type="binary", multi="_get_image",
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 50x50 px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required."),
'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."),
@ -320,16 +315,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_default_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.image_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_default_image,
'active' : True,
'menu_id': _get_menu,
'company_id': _get_company,
@ -338,7 +333,7 @@ class users(osv.osv):
}
# User can write to a few of her own fields (but not her groups for example)
SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'user_email', 'name', 'avatar', 'avatar_big']
SELF_WRITEABLE_FIELDS = ['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__'):
@ -539,6 +534,19 @@ class users(osv.osv):
return self.write(cr, uid, uid, {'password': new_passwd})
raise osv.except_osv(_('Warning!'), _("Setting empty passwords is not allowed for security reasons!"))
def preference_save(self, cr, uid, ids, context=None):
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def preference_change_password(self, cr, uid, ids, context=None):
return {
'type': 'ir.actions.client',
'tag': 'change_password',
'target': 'new',
}
def has_group(self, cr, uid, group_ext_id):
"""Checks whether user belongs to given group.

View File

@ -91,7 +91,7 @@
<form string="Users" version="7.0">
<field name="id" invisible="1"/>
<sheet>
<field name="avatar" widget='image' on_change="onchange_avatar(avatar)" class="oe_avatar oe_left"/>
<field name="image_medium" widget='image' class="oe_avatar oe_left"/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
@ -201,9 +201,8 @@
<field eval="18" name="priority"/>
<field name="arch" type="xml">
<form string="Users" version="7.0">
<sheet>
<div class="oe_right oe_avatar">
<field name="avatar" widget='image' on_change="onchange_avatar(avatar)"/>
<field name="image_small" widget='image' class="oe_image_small"/>
</div>
<h1>
<field name="name" readonly="1" class="oe_inline"/>
@ -221,7 +220,11 @@
<field name="user_email" widget="email" readonly="0"/>
<field name="signature" readonly="0"/>
</group>
</sheet>
<footer>
<button name="preference_save" type="object" string="Save"/>
<button name="preference_cancel" special="cancel" string="Cancel"/>
<button name="preference_change_password" type="object" string="Change password"/>
</footer>
</form>
</field>
</record>
@ -229,14 +232,9 @@
<field name="name">Change My Preferences</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.users</field>
<field name="target">new</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
<field name="domain">[('id','=',uid)]</field>
</record>
<record id="action_res_users_my_view1" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="action_res_users_my"/>
<field name="view_mode">form</field>
</record>
<record id="action_res_users_my_view2" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>

View File

@ -68,10 +68,7 @@ def initialize(cr):
category_id = create_categories(cr, categories)
if info['installable']:
if info['auto_install'] and not info['depends']:
state = 'to install'
else:
state = 'uninstalled'
state = 'uninstalled'
else:
state = 'uninstallable'
@ -95,7 +92,19 @@ def initialize(cr):
for d in dependencies:
cr.execute('INSERT INTO ir_module_module_dependency \
(module_id,name) VALUES (%s, %s)', (id, d))
cr.commit()
# Install recursively all auto-installing modules
while True:
cr.execute("""SELECT m.name FROM ir_module_module m WHERE m.auto_install AND state != 'to install'
AND NOT EXISTS (
SELECT 1 FROM ir_module_module_dependency d JOIN ir_module_module mdep ON (d.name = mdep.name)
WHERE d.module_id = m.id AND mdep.state != 'to install'
)""")
to_auto_install = [x[0] for x in cr.fetchall()]
if not to_auto_install: break
cr.execute("""UPDATE ir_module_module SET state='to install' WHERE name in %s""", (tuple(to_auto_install),))
cr.commit()
def create_categories(cr, categories):
""" Create the ir_module_category entries for some categories.

View File

@ -51,7 +51,7 @@
<field name="target">new</field>
</record>
<menuitem icon="STOCK_PREFERENCES" id="base.menu_tests" name="Tests"/>
<menuitem icon="STOCK_PREFERENCES" id="base.menu_tests" name="Tests" sequence="1000000"/>
<menuitem id="menu_test_exceptions" parent="base.menu_tests" name="Test exceptions"/>

View File

@ -26,6 +26,7 @@ from misc import *
from convert import *
from translate import *
from graph import graph
from image import *
from amount_to_text import *
from amount_to_text_en import *
from pdf_utils import *

139
openerp/tools/image.py Normal file
View File

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-today OpenERP s.a. (<http://openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import io
from PIL import Image
import StringIO
# ----------------------------------------
# Image resizing
# ----------------------------------------
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
""" Function to resize an image. The image will be resized to the given
size, while keeping the aspect ratios, and holes in the image will be
filled with transparent background. The image will not be stretched if
smaller than the expected size.
Steps of the resizing:
- if avoid_if_small: if both image sizes are smaller than the requested
sizes, the original image is returned. This is used to avoid adding
transparent content around images that we do not want to alter but
just resize if too big. This is used for example when storing images
in the 'image' field: we keep the original image, resized to a maximal
size, without adding transparent content around it if smaller.
- create a thumbnail of the source image through using the thumbnail
function. Aspect ratios are preserved when using it. Note that if the
source image is smaller than the expected size, it will not be
extended, but filled to match the size.
- create a transparent background that will hold the final
image.
- past the thumbnail on the transparent background and center
it.
:param base64_source: base64-encoded version of the source
image
:param size: tuple(height, width)
:param encoding: the output encoding
:param filetype: the output filetype
:param avoid_if_small: do not resize if image height and width
are smaller than the expected size.
"""
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 image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG'):
""" Wrapper on image_resize_image, to resize images larger than the standard
'big' image size: 1024x1024px.
:param base64_source: base64 encoded source image. If False,
the function returns False.
"""
if not base64_source:
return False
return image_resize_image(base64_source, size, encoding, filetype, True)
def image_resize_image_medium(base64_source, size=(180, 180), encoding='base64', filetype='PNG'):
""" Wrapper on image_resize_image, to resize to the standard 'medium'
image size: 180x180.
:param base64_source: base64 encoded source image. If False,
the function returns False.
"""
if not base64_source:
return False
return image_resize_image(base64_source, size, encoding, filetype)
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
size: 50x50.
:param base64_source: base64 encoded source image. If False,
the function returns False.
"""
if not base64_source:
return False
return image_resize_image(base64_source, size, encoding, filetype)
# ----------------------------------------
# Misc image tools
# ---------------------------------------
def image_get_resized_images(base64_source, return_big=False, return_medium=True, return_small=True,
big_name='image', medium_name='image_medium', small_name='image_small'):
""" Standard tool function that returns a dictionary containing the
big, medium and small versions of the source image. This function
is meant to be used for the methods of functional fields for
models using images.
Default parameters are given to be used for the getter of functional
image fields, for example with res.users or res.partner. It returns
only image_medium and image_small values, to update those fields.
: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
OpenERP and that are binary fields.
:param return_big: if set, return_dict contains the 'big_name' entry
:param return_medium: if set, return_dict contains the 'medium_name' entry
:param return_small: if set, return_dict contains the 'small_name' entry
:param big_name: name related to the big version of the image;
'image' by default.
:param medium_name: name related to the medium version of the
image; 'image_medium' by default.
:param small_name: name related to the small version of the
image; 'image_small' by default.
:return return_dict: dictionary with resized images, depending on
previous parameters.
"""
return_dict = dict()
if return_big: return_dict[big_name] = image_resize_image_big(base64_source)
if return_medium: 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

View File

@ -279,7 +279,11 @@ email_re = re.compile(r"""
""", re.VERBOSE)
res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
reference_re = re.compile("<.*-open(?:object|erp)-(\\d+).*@(.*)>", re.UNICODE)
# Updated in 7.0 to match the model name as well
# Typical form of references is <timestamp-openerp-record_id-model_name@domain>
# group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain
reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?.*@(.*)>", re.UNICODE)
def html2plaintext(html, body_id=None, encoding='utf-8'):
""" From an HTML text, convert the HTML to plain text.