[MERGE] latest trunk
bzr revid: abo@openerp.com-20120810100327-aggitnea3uefcv2m
This commit is contained in:
@@ -56,6 +56,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',
|
||||
|
||||
@@ -7,14 +7,14 @@ msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 5.0.0\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-02-08 00:44+0000\n"
|
||||
"PO-Revision-Date: 2012-02-19 13:01+0000\n"
|
||||
"PO-Revision-Date: 2012-08-09 06:15+0000\n"
|
||||
"Last-Translator: Erwin <Unknown>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-07-19 04:38+0000\n"
|
||||
"X-Generator: Launchpad (build 15637)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-10 04:58+0000\n"
|
||||
"X-Generator: Launchpad (build 15761)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.sh
|
||||
@@ -1443,7 +1443,7 @@ msgstr ""
|
||||
"U kunt de navolgende extra data toevoegen aan een verkooporder:\n"
|
||||
"* Aanvraagdatum\n"
|
||||
"* Bevestigde datum\n"
|
||||
"* Effectieve datum\n"
|
||||
"* Boek datum\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_account_sequence
|
||||
@@ -8110,7 +8110,7 @@ msgstr "Kenmerk"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_analytic
|
||||
msgid "Analytic Accounting"
|
||||
msgstr "Kostenplaats boekhouding"
|
||||
msgstr "Kostenplaatsen"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.actions.report.xml:0
|
||||
|
||||
@@ -177,8 +177,8 @@ class module(osv.osv):
|
||||
_columns = {
|
||||
'name': fields.char("Technical Name", size=128, readonly=True, required=True, select=True),
|
||||
'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True),
|
||||
'shortdesc': fields.char('Module Name', size=256, readonly=True, translate=True),
|
||||
'summary': fields.char('Summary', size=256, readonly=True, translate=True),
|
||||
'shortdesc': fields.char('Module Name', size=64, readonly=True, translate=True),
|
||||
'summary': fields.char('Summary', size=64, readonly=True, translate=True),
|
||||
'description': fields.text("Description", readonly=True, translate=True),
|
||||
'author': fields.char("Author", size=128, readonly=True),
|
||||
'maintainer': fields.char('Maintainer', size=128, readonly=True),
|
||||
@@ -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)
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<form string="Import Translation" version="7.0">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="code" string="Code" placeholder="en_US"/>
|
||||
<field name="code" string="Code" placeholder="e.g. en_US"/>
|
||||
<field name="data"/>
|
||||
<field name="overwrite"/>
|
||||
</group>
|
||||
|
||||
@@ -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>
|
||||
@@ -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'),
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</h1>
|
||||
<label for="rml_header1" class="oe_edit_only"/>
|
||||
<div>
|
||||
<field name="rml_header1" placeholder="Global Business Solutions"/>
|
||||
<field name="rml_header1" placeholder="e.g. Global Business Solutions"/>
|
||||
</div>
|
||||
</div>
|
||||
<group col="4">
|
||||
|
||||
@@ -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,9 @@ 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),
|
||||
'image_small': lambda self, cr, uid, context: self._get_default_image(cr, uid, False, context),
|
||||
'image_medium': 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 +244,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 +311,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 +361,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:
|
||||
|
||||
@@ -99,20 +99,21 @@
|
||||
<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"/> (
|
||||
<field name="is_company" on_change="onchange_type(is_company)" class="oe_inline"/> <label for="is_company" string="Is a Company?"/>)
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name" />
|
||||
</h1>
|
||||
<field name="parent_id"
|
||||
string="Work for Company"
|
||||
placeholder="Company"
|
||||
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
|
||||
attrs="{'invisible': [('is_company','=', True)]}"
|
||||
on_change="onchange_address(use_parent_address, parent_id)"/>
|
||||
<div class="oe_edit_only">
|
||||
<field name="is_company" on_change="onchange_type(is_company)" class="oe_inline"/> <label for="is_company" string="Company?"/>
|
||||
<field name="category_id" widget="many2many_tags" placeholder="Select Tags"/>
|
||||
</div>
|
||||
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
|
||||
</div>
|
||||
<div class="oe_right oe_button_box" name="buttons"> </div>
|
||||
|
||||
@@ -148,6 +149,7 @@
|
||||
<field name="fax"/>
|
||||
<field name="email" widget="email"/>
|
||||
<field name="title" domain="[('domain', '=', 'contact')]"
|
||||
groups="base.group_no_one"
|
||||
options='{"no_open": true}' attrs="{'invisible': [('is_company','=', True)]}" />
|
||||
</group>
|
||||
</group>
|
||||
@@ -166,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"/>
|
||||
@@ -179,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">
|
||||
@@ -205,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 & Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
|
||||
<group>
|
||||
<group>
|
||||
@@ -215,7 +233,7 @@
|
||||
</group>
|
||||
<group>
|
||||
<field name="customer"/>
|
||||
<field name="supplier" invisible="1"/>
|
||||
<field name="supplier" invisible="not context.get('default_supplier')"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="ref"/>
|
||||
@@ -230,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>
|
||||
@@ -256,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...">
|
||||
@@ -284,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"/>
|
||||
@@ -295,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>
|
||||
|
||||
@@ -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,18 @@ 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,
|
||||
'image_small': _get_default_image,
|
||||
'image_medium': _get_default_image,
|
||||
'active' : True,
|
||||
'menu_id': _get_menu,
|
||||
'company_id': _get_company,
|
||||
@@ -338,7 +335,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 +536,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.
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
+14
-5
@@ -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.
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user