Dharti Ratani (OpenERP) 2012-08-09 10:52:05 +05:30
commit 2cc64dd7d6
39 changed files with 782 additions and 537 deletions

View File

@ -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',
@ -96,6 +97,6 @@
'installable': True,
'auto_install': True,
'certificate': '0076807797149',
"css": [ 'static/src/css/modules.css' ],
'css': ['static/src/css/modules.css'],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:44+0000\n"
"PO-Revision-Date: 2012-07-19 05:44+0000\n"
"PO-Revision-Date: 2012-08-02 17:18+0000\n"
"Last-Translator: Akira Hiyama <Unknown>\n"
"Language-Team: Japanese <ja@li.org>\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-20 04:37+0000\n"
"X-Generator: Launchpad (build 15644)\n"
"X-Launchpad-Export-Date: 2012-08-03 05:45+0000\n"
"X-Generator: Launchpad (build 15734)\n"
#. module: base
#: model:res.country,name:base.sh
@ -283,7 +283,7 @@ msgstr "イヌクウティトット語 / Inuktitut"
#: model:ir.module.category,name:base.module_category_sales_management
#: model:ir.module.module,shortdesc:base.module_sale
msgid "Sales Management"
msgstr "セールス管理"
msgstr "受注管理"
#. module: base
#: view:res.partner:0
@ -1002,7 +1002,7 @@ msgstr "オマーン"
#. module: base
#: model:ir.module.module,shortdesc:base.module_mrp
msgid "MRP"
msgstr "MRP(資材所要量計画)"
msgstr "MRP"
#. module: base
#: report:ir.module.reference.graph:0
@ -3795,7 +3795,7 @@ msgstr "ホスト名またはSMTPサーバのIPアドレス"
#. module: base
#: selection:base.language.install,lang:0
msgid "Japanese / 日本語"
msgstr "Japanese / 日本語"
msgstr "日本語 / Japanese"
#. module: base
#: field:ir.actions.report.xml,auto:0
@ -6423,7 +6423,7 @@ msgstr "多対1項目の属性削除"
#. module: base
#: model:ir.module.category,name:base.module_category_accounting_and_finance
msgid "Accounting & Finance"
msgstr "会計と金融"
msgstr "会計と財務"
#. module: base
#: field:ir.actions.server,write_id:0
@ -6961,7 +6961,7 @@ msgstr ""
#: field:res.request,act_to:0
#: field:res.request.history,act_to:0
msgid "To"
msgstr ""
msgstr "宛先"
#. module: base
#: view:ir.sequence:0
@ -7472,7 +7472,7 @@ msgstr "アクセスの作成"
#: field:res.partner.address,state_id:0
#: field:res.partner.bank,state_id:0
msgid "Fed. State"
msgstr ""
msgstr "都道府県"
#. module: base
#: field:ir.actions.server,copy_object:0
@ -7703,7 +7703,7 @@ msgstr "販売員"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_accountant
msgid "Accounting and Finance"
msgstr "会計と金融"
msgstr "会計と財務"
#. module: base
#: code:addons/base/module/module.py:429
@ -8215,7 +8215,7 @@ msgstr "中国語 / 简体中文"
#: field:res.partner.address,street:0
#: field:res.partner.bank,street:0
msgid "Street"
msgstr ""
msgstr "町名番地"
#. module: base
#: model:res.country,name:base.yu
@ -11238,7 +11238,7 @@ msgid ""
"(default: 465)"
msgstr ""
"接続の暗号化方式を選択:\n"
"・ なし: SMTPセッションはクリアテキストで行われ。\n"
"・ なし: SMTPセッションはクリアテキストで行われます。\n"
"・ TLSSTARTTLSTLS暗号化はSMTPセッションの開始において要求されます推奨。\n"
"・ SSL/TLSSMTPセッションは専用のポートデフォルト465によってSSL/TLSで暗号化されます。"
@ -12022,7 +12022,7 @@ msgstr "%m - 月[01,12]."
#: field:res.partner.address,city:0
#: field:res.partner.bank,city:0
msgid "City"
msgstr "市"
msgstr "市区町村"
#. module: base
#: model:res.country,name:base.qa
@ -12413,7 +12413,7 @@ msgstr "あなたが作成 / 書き込みたいオブジェクト。もし、そ
#: selection:ir.module.module,state:0
#: selection:ir.module.module.dependency,state:0
msgid "Not Installed"
msgstr "インストールされていません。"
msgstr "インストール"
#. module: base
#: view:workflow.activity:0
@ -12982,7 +12982,7 @@ msgstr "発注時の二重検証"
#: field:res.company,street2:0
#: field:res.partner.address,street2:0
msgid "Street2"
msgstr ""
msgstr "町名番地2"
#. module: base
#: model:ir.actions.act_window,name:base.action_view_base_module_update

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:44+0000\n"
"PO-Revision-Date: 2012-01-31 16:29+0000\n"
"Last-Translator: Antony Lesuisse (OpenERP) <al@openerp.com>\n"
"PO-Revision-Date: 2012-07-26 16:30+0000\n"
"Last-Translator: Rinat Karimov <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:42+0000\n"
"X-Generator: Launchpad (build 15637)\n"
"X-Launchpad-Export-Date: 2012-07-27 05:22+0000\n"
"X-Generator: Launchpad (build 15694)\n"
#. module: base
#: model:res.country,name:base.sh
@ -2732,7 +2732,7 @@ msgstr "Очистить ID"
#. module: base
#: view:res.groups:0
msgid "Inherited"
msgstr ""
msgstr "Унаследованный"
#. module: base
#: field:ir.model.fields,serialization_field_id:0
@ -6614,7 +6614,7 @@ msgstr "Совет"
#. module: base
#: view:res.company:0
msgid "Header/Footer of Reports"
msgstr ""
msgstr "Заголовок/подвал отчетов"
#. module: base
#: code:addons/base/res/res_users.py:746

View File

@ -71,7 +71,6 @@
<field name="name"
filter_domain="['|', '|', ('name','ilike',self), ('model','ilike',self), ('key2','ilike',self)]"
string="Client Action"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Model" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'key2'}"/>
@ -203,7 +202,6 @@
<search string="Sequences">
<field name="name" string="Sequence"/>
<field name="code"/>
<separator orientation="vertical"/>
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
@ -392,12 +390,9 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Report Xml">
<group col="10" colspan="4">
<field name="name"
filter_domain="['|', '|', '|', '|', ('name','ilike',self), ('model','ilike',self), ('type','ilike',self), ('report_name','ilike',self), ('report_type','ilike',self)]"
string="Report"/>
</group>
<newline/>
<field name="name"
filter_domain="['|', '|', '|', '|', ('name','ilike',self), ('model','ilike',self), ('type','ilike',self), ('report_name','ilike',self), ('report_type','ilike',self)]"
string="Report"/>
<group expand="0" string="Group By" colspan="4">
<filter string="Report Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'report_type'}"/>
</group>
@ -499,11 +494,8 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Open a Window">
<group col="10" colspan="4">
<field name="name" filter_domain="['|', ('name','ilike',self), ('res_model','ilike',self)]" string="Action"/>
<field name="view_type"/>
</group>
<newline/>
<field name="name" filter_domain="['|', ('name','ilike',self), ('res_model','ilike',self)]" string="Action"/>
<field name="view_type"/>
<group expand="0" string="Group By" colspan="4">
<filter string="View Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'view_type'}"/>
</group>
@ -661,7 +653,6 @@
<field name="arch" type="xml">
<search string="Views">
<field name="name" filter_domain="['|', ('name','ilike',self), ('model','ilike',self)]" string="View"/>
<separator orientation="vertical"/>
<filter icon="terp-stock_zoom"
string="Search"
domain="[('type', '=', 'search')]"/>
@ -671,10 +662,8 @@
<filter icon="gtk-new"
string="Form"
domain="[('type', '=','form')]"/>
<separator orientation="vertical"/>
<field name="inherit_id"/>
<field name="type"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Object" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'type'}"/>
@ -802,31 +791,26 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Attachments">
<group colspan="4" col="10">
<field name="name" filter_domain="['|', ('name','ilike',self), ('datas_fname','ilike',self)]" string="Attachment"/>
<field name="create_date"/>
<separator orientation="vertical"/>
<filter icon="terp-stage"
string="URL"
domain="[('type','=','url')]"/>
<filter icon="terp-stock_align_left_24"
string="Binary"
domain="[('type','=','binary')]"/>
<filter name="my_documents_filter"
string="My Document(s)"
icon="terp-personal"
domain="[('create_uid','=',uid)]"
help="Filter on my documents"/>
<separator orientation="vertical"/>
<field name="create_uid"/>
<field name="type"/>
</group>
<newline/>
<field name="name" filter_domain="['|', ('name','ilike',self), ('datas_fname','ilike',self)]" string="Attachment"/>
<field name="create_date"/>
<filter icon="terp-stage"
string="URL"
domain="[('type','=','url')]"/>
<filter icon="terp-stock_align_left_24"
string="Binary"
domain="[('type','=','binary')]"/>
<separator/>
<filter name="my_documents_filter"
string="My Document(s)"
icon="terp-personal"
domain="[('create_uid','=',uid)]"
help="Filter on my documents"/>
<field name="create_uid"/>
<field name="type"/>
<group expand="0" string="Group By...">
<filter string="Owner" icon="terp-personal" domain="[]" context="{'group_by':'create_uid'}"/>
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'type'}" groups="base.group_no_one"/>
<filter string="Company" icon="terp-gtk-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<separator orientation="vertical"/>
<filter string="Month" help="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}"/>
</group>
</search>
@ -955,10 +939,10 @@
<field name="arch" type="xml">
<search string="Model Description">
<field name="name" filter_domain="['|', ('name','ilike',self), ('model','ilike',self)]" string="Model"/>
<separator orientation="vertical"/>
<filter icon="terp-camera_test"
string="In Memory"
domain="[('osv_memory', '=', True)]"/>
<separator/>
<filter icon="terp-stock_align_left_24"
string="Custom"
domain="[('state', '=', 'manual')]"/>
@ -1035,22 +1019,21 @@
<field name="arch" type="xml">
<search string="Fields">
<field name="name" filter_domain="['|', ('name','ilike',self), ('field_description','ilike',self)]" string="Field"/>
<separator orientation="vertical"/>
<filter icon="terp-gnome-cpu-frequency-applet+"
string="Required"
domain="[('required', '=', True)]"/>
<separator/>
<filter icon="terp-dialog-close"
string="Readonly"
domain="[('readonly', '=', True)]"/>
<separator/>
<filter icon="terp-translate"
string="Translate"
domain="[('translate', '=', True)]"/>
<newline/>
<field name="model_id"/>
<field name="ttype"/>
<field name="required"/>
<field name="readonly"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Object" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model_id'}"/>
</group>
@ -1107,14 +1090,11 @@
<field name="name"
filter_domain="['|', '|', ('name','ilike',self), ('model','ilike',self), ('module','ilike',self)]"
string="External Identifier"/>
<separator orientation="vertical"/>
<filter icon="terp-camera_test"
string="Updatable"
domain="[('noupdate', '=', False)]"/>
<separator orientation="vertical"/>
<field name="res_id"/>
<field name="noupdate"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Module" icon="terp-folder-blue" domain="[]" context="{'group_by':'module'}"/>
<filter string="Object" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
@ -1240,7 +1220,6 @@
<filter icon="terp-gdu-smart-failing"
string="Untranslated"
domain="['|',('value', '=', False),('value','=','')]"/>
<separator orientation="vertical"/>
<field name="lang"/>
<field name="src"/>
<field name="value"/>
@ -1469,11 +1448,9 @@
<field name="arch" type="xml">
<search string="Scheduled Actions">
<field name="name" string="Scheduled Action"/>
<separator orientation="vertical"/>
<field name="user_id" widget="selection"/>
<field name="user_id"/>
<field name="nextcall"/>
<field name="active"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="User" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Execution" icon="terp-go-month"
@ -1554,18 +1531,14 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Access Controls">
<group>
<field name="name" string="Access Control"/>
<separator orientation="vertical"/>
<filter string="Global" icon="terp-stage" domain="[('group_id','=',False)]"/>
<filter string="Full Access" icon="terp-gtk-select-all" domain="[('perm_read','=',True),('perm_write','=',True),('perm_create','=',True),('perm_unlink','=',True)]"/>
<filter string="Read Access" icon="terp-stock_align_left_24" domain="[('perm_read','=',True)]"/>
<filter string="Write Access" icon="terp-tools" domain="[('perm_write','=',True)]"/>
<separator orientation="vertical"/>
<field name="model_id"/>
<field name="group_id"/>
</group>
<newline/>
<field name="name" string="Access Control"/>
<filter string="Global" icon="terp-stage" domain="[('group_id','=',False)]"/>
<separator/>
<filter string="Full Access" icon="terp-gtk-select-all" domain="[('perm_read','=',True),('perm_write','=',True),('perm_create','=',True),('perm_unlink','=',True)]"/>
<filter string="Read Access" icon="terp-stock_align_left_24" domain="[('perm_read','=',True)]"/>
<filter string="Write Access" icon="terp-tools" domain="[('perm_write','=',True)]"/>
<field name="model_id"/>
<field name="group_id"/>
<group expand="0" string="Group By..." colspan="11" col="11" groups="base.group_no_one">
<filter string="Group" icon="terp-personal" domain="[]" context="{'group_by':'group_id'}"/>
<filter string="Object" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model_id'}"/>
@ -1651,17 +1624,14 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Record Rules">
<group>
<field name="name" string="Record Rule"/>
<separator orientation="vertical"/>
<filter string="Global" icon="terp-stage" domain="[('global','=',True)]"/>
<filter string="Full Access" icon="terp-gtk-select-all" domain="[('perm_read','=',True),('perm_write','=',True),('perm_create','=',True),('perm_unlink','=',True)]"/>
<filter string="Read Access" icon="terp-stock_align_left_24" domain="[('perm_read','=',True)]"/>
<filter string="Write Access" icon="terp-tools" domain="[('perm_write','=',True)]"/>
<separator orientation="vertical"/>
<field name="model_id"/>
<field name="groups"/>
</group>
<field name="name" string="Record Rule"/>
<filter string="Global" icon="terp-stage" domain="[('global','=',True)]"/>
<separator/>
<filter string="Full Access" icon="terp-gtk-select-all" domain="[('perm_read','=',True),('perm_write','=',True),('perm_create','=',True),('perm_unlink','=',True)]"/>
<filter string="Read Access" icon="terp-stock_align_left_24" domain="[('perm_read','=',True)]"/>
<filter string="Write Access" icon="terp-tools" domain="[('perm_write','=',True)]"/>
<field name="model_id"/>
<field name="groups"/>
</search>
</field>
</record>
@ -1794,13 +1764,9 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Server Actions">
<group col="10" colspan="4">
<field name="name" string="Server Action"/>
<separator orientation="vertical"/>
<field name="model_id"/>
<field name="state"/>
</group>
<newline/>
<field name="name" string="Server Action"/>
<field name="model_id"/>
<field name="state"/>
<group expand="0" string="Group By" colspan="4" col="4">
<filter string="Action Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'state'}"/>
</group>
@ -1830,7 +1796,6 @@
<tree string="Config Wizard Steps">
<field name="sequence"/>
<field name="action_id"/>
<field name="category_id"/>
<field name="type"/>
<field name="state" readonly="1"/>
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
@ -1862,7 +1827,6 @@
<field name="action_id"/>
<field name="type"/>
<field name="sequence"/>
<field name="category_id"/>
</group>
<group string="Groups">
<field name="groups_id" nolabel="1" colspan="4"/>
@ -1878,17 +1842,9 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Actions">
<group>
<filter string="To Do" name="todo" icon="terp-camera_test" domain=" [('state','=','open')]" help="Wizards to be Launched"/>
<separator orientation="vertical"/>
<field name="action_id"/>
<field name="category_id"/>
<field name="state"/>
</group>
<newline/>
<group expand="0" string="Group By...">
<filter string="Category" context="{'group_by': 'category_id'}" icon="terp-folder-orange"/>
</group>
<filter string="To Do" name="todo" icon="terp-camera_test" domain=" [('state','=','open')]" help="Wizards to be Launched"/>
<field name="action_id"/>
<field name="state"/>
</search>
</field>
</record>
@ -1915,50 +1871,6 @@
<field name="args">()</field>
</record>
<!-- ir.actions.todo category -->
<record id="ir_actions_todo_category_form" model="ir.ui.view">
<field name="name">ir.actions.todo.category.form</field>
<field name="model">ir.actions.todo.category</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Wizard Category" version="7.0">
<group col="4">
<field name="name"/>
<field name="sequence"/>
<field name="wizards_ids" nolabel="1" colspan="4"/>
</group>
</form>
</field>
</record>
<record id="ir_actions_todo_category_tree" model="ir.ui.view">
<field name="name">ir.actions.todo.category.tree</field>
<field name="model">ir.actions.todo.category</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Wizard Category">
<field name="name"/>
<field name="sequence"/>
</tree>
</field>
</record>
<record id="category_administration_config" model="ir.actions.todo.category">
<field name="name">Administration</field>
<field name="sequence">1</field>
</record>
<record id="category_sales_management_config" model="ir.actions.todo.category">
<field name="name">Sales Management</field>
<field name="sequence">5</field>
</record>
<record id="category_tools_customization_config" model="ir.actions.todo.category">
<field name="name">Tools / Customization</field>
<field name="sequence">5</field>
</record>
<!-- ir.mail.server -->
<record model="ir.ui.view" id="ir_mail_server_form">
<field name="name">ir.mail.server.form</field>

View File

@ -749,20 +749,6 @@ class act_window_close(osv.osv):
}
act_window_close()
class ir_actions_todo_category(osv.osv):
"""
Category of Configuration Wizards
"""
_name = 'ir.actions.todo.category'
_description = "Configuration Wizard Category"
_columns = {
'name':fields.char('Name', size=64, translate=True, required=True),
'sequence': fields.integer('Sequence'),
'wizards_ids': fields.one2many('ir.actions.todo', 'category_id', 'Configuration Wizards'),
}
ir_actions_todo_category()
# This model use to register action services.
TODO_STATES = [('open', 'To Do'),
('done', 'Done')]
@ -776,8 +762,7 @@ class ir_actions_todo(osv.osv):
_description = "Configuration Wizards"
_columns={
'action_id': fields.many2one(
'ir.actions.act_window', 'Action', select=True, required=True,
ondelete='cascade'),
'ir.actions.actions', 'Action', select=True, required=True),
'sequence': fields.integer('Sequence'),
'state': fields.selection(TODO_STATES, string='State', required=True),
'name': fields.char('Name', size=64),
@ -787,14 +772,13 @@ Automatic: Runs whenever the system is reconfigured.
Launch Manually Once: after hacing been launched manually, it sets automatically to Done."""),
'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
'note': fields.text('Text', translate=True),
'category_id': fields.many2one('ir.actions.todo.category','Category'),
}
_defaults={
'state': 'open',
'sequence': 10,
'type': 'manual',
}
_order="sequence,name,id"
_order="sequence,id"
def action_launch(self, cr, uid, ids, context=None):
""" Launch Action of Wizard"""
@ -804,7 +788,11 @@ Launch Manually Once: after hacing been launched manually, it sets automatically
wizard.write({'state': 'done'})
# Load action
res = self.pool.get('ir.actions.act_window').read(cr, uid, wizard.action_id.id, [], context=context)
act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
if act_type<>'ir.actions.act_window':
return res
res.setdefault('context','{}')
res['nodestroy'] = True
@ -879,8 +867,10 @@ class act_client(osv.osv):
])
def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
assert isinstance(field_value, dict), "params can only be dictionaries"
self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
if isinstance(field_value, dict):
self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
else:
self.write(cr, uid, id, {'params_store': field_value}, context=context)
_columns = {
'tag': fields.char('Client action tag', size=64, required=True,

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
@ -34,7 +35,7 @@
<field name="key"/>
<field name="value"/>
</group>
</sheet>
</sheet>
</form>
</field>
</record>

View File

@ -54,13 +54,12 @@
<field name="arch" type="xml">
<search string="Filters">
<field name="name" string="Filter Name"/>
<separator orientation="vertical"/>
<filter string="Personal" domain="[('user_id','!=',False)]" help="Filters visible only for one user"/>
<filter string="Shared" domain="[('user_id','=',False)]" help="Filters shared with all users"/>
<separator/>
<filter icon="terp-personal" domain="[('user_id','in',(uid, False))]"
name="my_filters"
string="My Filters"/>
<separator orientation="vertical"/>
<field name="model_id"/>
<field name="user_id"/>
</search>

View File

@ -178,14 +178,16 @@ class ir_model(osv.osv):
def create(self, cr, user, vals, context=None):
if context is None:
context = {}
if context and context.get('manual',False):
if context and context.get('manual'):
vals['state']='manual'
res = super(ir_model,self).create(cr, user, vals, context)
if vals.get('state','base')=='manual':
self.instanciate(cr, user, vals['model'], context)
self.pool.get(vals['model']).__init__(self.pool, cr)
ctx = context.copy()
ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
ctx = dict(context,
field_name=vals['name'],
field_state='manual',
select=vals.get('select_level', '0'))
self.pool.get(vals['model'])._auto_init(cr, ctx)
#pooler.restart_pool(cr.dbname)
return res
@ -335,8 +337,11 @@ class ir_model_fields(osv.osv):
if self.pool.get(vals['model']):
self.pool.get(vals['model']).__init__(self.pool, cr)
#Added context to _auto_init for special treatment to custom field for select_level
ctx = context.copy()
ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
ctx = dict(context,
field_name=vals['name'],
field_state='manual',
select=vals.get('select_level', '0'),
update_custom_fields=True)
self.pool.get(vals['model'])._auto_init(cr, ctx)
return res
@ -446,8 +451,8 @@ class ir_model_fields(osv.osv):
# was called earlier, they will be in-sync before the _auto_init.
# Anything we don't update in _columns now will be reset from
# the model into ir.model.fields (db).
ctx = context.copy()
ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
ctx = dict(context, select=vals.get('select_level', '0'),
update_custom_fields=True)
for __, patch_struct in models_patch.items():
obj = patch_struct[0]
@ -825,20 +830,16 @@ class ir_model_data(osv.osv):
'res_id': inherit_id.id,
'noupdate': noupdate,
},context=context)
if xml_id:
if res_id:
self.loads[(module, xml_id)] = (model, res_id)
if model_obj._inherits:
for table in model_obj._inherits:
inherit_field = model_obj._inherits[table]
inherit_id = model_obj.read(cr, uid, res_id,
[inherit_field])[inherit_field]
self.loads[(module, xml_id + '_' + \
table.replace('.', '_'))] = (table, inherit_id)
if xml_id and res_id:
self.loads[(module, xml_id)] = (model, res_id)
for table, inherit_field in model_obj._inherits.iteritems():
inherit_id = model_obj.read(cr, uid, res_id,
[inherit_field])[inherit_field]
self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
return res_id
def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
if type(models[0])==type([]) or type(models[0])==type(()):
if isinstance(models[0], (list, tuple)):
model,res_id = models[0]
else:
res_id=None
@ -858,7 +859,7 @@ class ir_model_data(osv.osv):
res = cr.fetchone()
if not res:
ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
elif xml_id:
cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
return True
@ -890,8 +891,6 @@ class ir_model_data(osv.osv):
for data in self.browse(cr, uid, ids, context):
model = data.model
res_id = data.res_id
model_obj = self.pool.get(model)
name = tools.ustr(data.name)
pair_to_unlink = (model, res_id)
if pair_to_unlink not in to_unlink:
@ -909,19 +908,19 @@ class ir_model_data(osv.osv):
for model,res_id in wkf_todo:
try:
wf_service.trg_write(uid, model, res_id, cr)
except:
_logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
except Exception:
_logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model, exc_info=True)
def unlink_if_refcount(to_unlink):
for model, res_id in to_unlink:
external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if (set(external_ids)-ids_set):
if set(external_ids)-ids_set:
# if other modules have defined this record, we must not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
except:
except Exception:
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
# Remove non-model records first, then model fields, and finish with models

View File

@ -164,16 +164,13 @@
<field name="arch" type="xml">
<search string="Workflow Activity">
<field name="name" string="Workflow Activity"/>
<separator orientation="vertical"/>
<filter icon="terp-camera_test" string="Flow Start"
domain="[('flow_start', '=',True)]" />
<filter icon="terp-gtk-stop" string="Flow Stop"
domain="[('flow_stop', '=',True)]" />
<separator orientation="vertical"/>
<field name="wkf_id"/>
<field name="action_id"/>
<field name="kind"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Workflow" icon="terp-stage" domain="[]" context="{'group_by':'wkf_id'}"/>
</group>
@ -242,7 +239,6 @@
<field name="arch" type="xml">
<search string="Transition">
<field name="signal" filter_domain="['|', ('signal','ilike',self), ('condition','ilike',self)]" string="Workflow Transition"/>
<separator orientation="vertical"/>
<field name="act_from"/>
<field name="act_to"/>
</search>
@ -301,10 +297,8 @@
<field name="arch" type="xml">
<search string="Workflow Instances">
<field name="res_type" string="Resource Object"/>
<separator orientation="vertical"/>
<filter icon="terp-camera_test" string="Active" domain="[('state','=','active')]" name="active"/>
<separator orientation="vertical"/>
<field name="wkf_id" widget="selection"/>
<field name="wkf_id"/>
<field name="res_id"/>
<field name="state"/>
</search>
@ -366,10 +360,8 @@
<field name="arch" type="xml">
<search string="Workflow Workitems">
<field name="state" string="State"/>
<separator orientation="vertical"/>
<filter icon="terp-camera_test" string="Active" name="active" domain="[('state','=','active')]"/>
<separator orientation="vertical"/>
<field name="wkf_id" widget="selection"/>
<field name="wkf_id"/>
<field name="act_id"/>
<field name="subflow_id"/>
<field name="inst_id"/>

View File

@ -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)

View File

@ -91,7 +91,7 @@
</record>
<record model="ir.module.category" id="module_category_point_of_sale">
<field name="name">Point of Sales</field>
<field name="name">Point of Sale</field>
<field name="description">Helps you get the most out of your points of sales with fast sale encoding, simplified payment mode encoding, automatic picking lists generation and more.</field>
<field name="sequence">13</field>
</record>
@ -132,6 +132,11 @@
<record model="res.groups" id="group_multi_company">
<field name="category_id" ref="module_category_usability"/>
</record>
<record model="res.groups" id="group_multi_currency">
<field name="category_id" ref="module_category_usability"/>
</record>
<record model="res.groups" id="group_no_one">
<field name="category_id" ref="module_category_usability"/>
</record>

View File

@ -41,21 +41,17 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search modules">
<group col='10' colspan='4'>
<field name="name" filter_domain="['|', ('name','ilike',self), ('shortdesc','ilike',self)]" string="Module"/>
<field name="description" string="Keywords" filter_domain="['|', ('description', 'ilike', self), ('summary', 'ilike', self)]"/>
<separator orientation="vertical"/>
<filter name="app" icon="terp-check" string="Apps" domain="[('application', '=', 1)]"/>
<filter name="extra" icon="terp-check" string="Extra" domain="[('application', '=', 0)]"/>
<filter icon="terp-check" string="Installed" domain="[('state', 'in', ['installed', 'to upgrade', 'to remove'])]"/>
<filter icon="terp-dialog-close" string="Not Installed" domain="[('state', 'in', ['uninstalled', 'uninstallable', 'to install'])]"/>
<separator orientation="vertical"/>
<field name="category_id"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Author" icon="terp-personal" domain="[]" context="{'group_by':'author'}"/>
<filter string="Category" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'category_id'}"/>
</group>
<field name="name" filter_domain="['|', '|', '|', ('description', 'ilike', self), ('summary', 'ilike', self), ('shortdesc', 'ilike', self), ('name',
'ilike', self)]"/>
<filter name="app" icon="terp-check" string="Apps" domain="[('application', '=', 1)]"/>
<filter name="extra" icon="terp-check" string="Extra" domain="[('application', '=', 0)]"/>
<separator/>
<filter icon="terp-check" string="Installed" domain="[('state', 'in', ['installed', 'to upgrade', 'to remove'])]"/>
<filter icon="terp-dialog-close" string="Not Installed" domain="[('state', 'in', ['uninstalled', 'uninstallable', 'to install'])]"/>
<field name="category_id"/>
<group expand="0" string="Group By...">
<filter string="Author" icon="terp-personal" domain="[]" context="{'group_by':'author'}"/>
<filter string="Category" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'category_id'}"/>
</group>
</search>
</field>
@ -120,9 +116,9 @@
<div class="oe_title">
<h1><field name="shortdesc"/></h1>
<h2 class="oe_fade"><field name="summary"/></h2>
<button name="button_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_install" states="uninstalled" string="Install" type="object" class="oe_highlight"/>
<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"/>
@ -131,7 +127,7 @@
<group>
<group>
<field name="author"/>
<field name="website" widget="url"/>
<field name="website" widget="url" attrs="{'invisible':[('website','=',False)]}"/>
<field name="category_id" widget="selection"/>
</group>
<group>
@ -144,12 +140,14 @@
<page string="Description">
<field name="description"/>
</page>
<page string="Technical Data">
<page string="Technical Data" groups="base.group_no_one">
<group col="4">
<field name="demo"/>
<field name="application"/>
<field name="state"/>
</group>
<label for="views_by_module" string="Created Views"/>
<field name="views_by_module"/>
<label for="dependencies_id"/>
<field name="dependencies_id">
<tree string="Dependencies">
@ -161,8 +159,6 @@
<page string="Features" attrs="{'invisible':[('state','!=','installed')]}">
<label for="menus_by_module" string="Created Menus"/>
<field name="menus_by_module"/>
<label for="views_by_module" string="Created Views"/>
<field name="views_by_module"/>
<label for="reports_by_module" string="Defined Reports"/>
<field name="reports_by_module"/>
</page>
@ -192,7 +188,10 @@
<field name="view_mode">kanban,tree,form</field>
<field name="context">{'search_default_app':1}</field>
<field name="search_view_id" ref="view_module_filter"/>
<field name="help">You can install new modules in order to activate new features, menu, reports or data in your OpenERP instance. To install some modules, click on the button "Install" from the form view and then click on "Start Upgrade".</field>
<field name="help" type="html">
<p><b>No module found!</b></p>
<p>You should try others search criteria.</p>
</field>
</record>
<menuitem id="menu_module_tree" parent="base.menu_management" name="Modules"
sequence="1" action="open_module_tree"/>

View File

@ -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>

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

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem id="menu_publisher_warranty" name="OpenERP Entreprise" parent="base.menu_administration" sequence="5"
<menuitem id="menu_publisher_warranty" name="OpenERP Enterprise" parent="base.menu_administration" sequence="5"
groups="base.group_no_one"/>
<record id="publisher_warranty_contract_tree_view" model="ir.ui.view">

View File

@ -9,12 +9,10 @@
<field name="arch" type="xml">
<search string="Parameters">
<field name="name" string="Name"/>
<separator orientation="vertical"/>
<filter icon="terp-project"
string="Generic"
help="Parameters that are used by all resources."
domain="[('res_id','=',False)]"/>
<separator orientation="vertical"/>
<field name="fields_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</search>

View File

@ -146,9 +146,7 @@
<field name="arch" type="xml">
<search string="Bank Accounts">
<field name="bank_name" filter_domain="['|', ('bank_name','ilike',self), ('acc_number','ilike',self)]" string="Bank Name"/>
<separator orientation="vertical"/>
<filter name="my_bank" icon="terp-check" string="My Banks" domain="[('company_id','&lt;&gt;',False)]" help="Bank accounts belonging to one of your companies"/>
<separator orientation="vertical"/>
<field name="company_id" invisible="context.get('company_hide', True)"/>
<field name="partner_id"/>
</search>

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

@ -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">
@ -178,8 +178,8 @@
<field name="arch" type="xml">
<search string="Multi Company">
<field name="name" string="Name"/>
<field name="company_id" widget="selection"/>
<field name="company_dest_id" widget="selection"/>
<field name="company_id"/>
<field name="company_dest_id"/>
<field name="object_id"/>
</search>
</field>

View File

@ -1,54 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="res_config_view_base" model="ir.ui.view">
<field name="name">res.config.view.base</field>
<field name="model">res.config</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form version="7.0">
<group string="res_config_contents"/>
<footer>
<button name="action_next" type="object" string="Apply" class="oe_highlight"/>
or
<button name="action_skip" type="object" special="cancel" string="Cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<data>
<record id="view_config_wizard_form" model="ir.ui.view">
<field name="name">Compabitiliby configuration wizard</field>
<field name="model">ir.actions.configuration.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Next Configuration Step" version="7.0">
<group>
<field name="note"/>
</group>
<footer>
<button name="action_next" type="object" string="Continue" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="res_config_view_base" model="ir.ui.view">
<field name="name">res.config.view.base</field>
<field name="model">res.config</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form version="7.0">
<group string="res_config_contents"/>
<footer>
<button name="action_next" type="object" string="Apply" class="oe_highlight"/>
or
<button name="action_skip" type="object" special="cancel" string="Cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<record id="res_config_installer" model="ir.ui.view">
<field name="name">Inheritable view for installer objects</field>
<field name="model">res.config.installer</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form version="7.0">
<separator string="title" colspan="4"/>
<footer>
<button name="action_next" type="object" string="Install Modules" class="oe_highlight"/>
or
<button string="Skip" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="view_config_wizard_form" model="ir.ui.view">
<field name="name">Compabitiliby configuration wizard</field>
<field name="model">ir.actions.configuration.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Next Configuration Step" version="7.0">
<group>
<field name="note"/>
</group>
<footer>
<button name="action_next" type="object" string="Continue" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
</data>
<record id="res_config_installer" model="ir.ui.view">
<field name="name">Inheritable view for installer objects</field>
<field name="model">res.config.installer</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form version="7.0">
<separator string="title" colspan="4"/>
<footer>
<button name="action_next" type="object" string="Install Modules" class="oe_highlight"/>
or
<button string="Skip" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -41,7 +41,7 @@
<field name="help">Display and manage the list of all countries that can be assigned to your partner records. You can create or delete countries to make sure the ones you are working on will be maintained.</field>
</record>
<menuitem id="menu_localisation" name="Localisation" parent="menu_config_address_book" sequence="1"/>
<menuitem id="menu_localisation" name="Localisation" parent="menu_config_address_book" sequence="1" groups="base.group_no_one"/>
<menuitem action="action_country" id="menu_country_partner" parent="menu_localisation" sequence="0" groups="base.group_no_one"/>

View File

@ -78,7 +78,7 @@
<field name="search_view_id" ref="view_currency_search"/>
</record>
<menuitem action="action_currency_form" id="menu_action_currency_form" parent="menu_localisation" sequence="3"/>
<menuitem action="action_currency_form" id="menu_action_currency_form" parent="menu_localisation" sequence="3" groups="base.group_multi_currency"/>
<!--
Currency Rate Type

View File

@ -108,15 +108,11 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Languages">
<group>
<field name="name"
filter_domain="['|', '|', ('name','ilike',self), ('code','ilike',self), ('iso_code','ilike',self)]"
string="Language"/>
<separator orientation="vertical"/>
<filter string="Translatable" icon="terp-tools" domain="[('translatable','=',True)]"/>
<separator orientation="vertical"/>
<field name="direction"/>
</group>
<field name="name"
filter_domain="['|', '|', ('name','ilike',self), ('code','ilike',self), ('iso_code','ilike',self)]"
string="Language"/>
<filter string="Translatable" icon="terp-tools" domain="[('translatable','=',True)]"/>
<field name="direction"/>
</search>
</field>
</record>

View File

@ -19,9 +19,10 @@
#
##############################################################################
import os
import math
import os
from osv import osv, fields
import re
import tools
from tools.translate import _
import logging
@ -103,12 +104,15 @@ class res_partner_category(osv.osv):
class res_partner_title(osv.osv):
_name = 'res.partner.title'
_order = 'name'
_columns = {
'name': fields.char('Title', required=True, size=46, translate=True),
'shortcut': fields.char('Abbreviation', required=True, size=16, translate=True),
'shortcut': fields.char('Abbreviation', size=16, translate=True),
'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24)
}
_order = 'name'
_defaults = {
'domain': 'contact',
}
def _lang_get(self, cr, uid, context=None):
lang_pool = self.pool.get('res.lang')
@ -129,12 +133,21 @@ 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),
'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title','Title'),
'parent_id': fields.many2one('res.partner','Company'),
'parent_id': fields.many2one('res.partner', 'Owned by'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'),
'ref': fields.char('Reference', size=64, select=1),
'lang': fields.selection(_lang_get, 'Language', help="If the selected language is loaded in the system, all documents related to this partner will be printed in this language. If not, it will be english."),
@ -160,7 +173,7 @@ class res_partner(osv.osv):
'street2': fields.char('Street2', size=128),
'zip': fields.char('Zip', change_default=True, size=24),
'city': fields.char('City', size=128),
'state_id': fields.many2one("res.country.state", 'State', domain="[('country_id','=',country_id)]"),
'state_id': fields.many2one("res.country.state", 'State'),
'country_id': fields.many2one('res.country', 'Country'),
'country': fields.related('country_id', type='many2one', relation='res.country', string='Country'), # for backward compatibility
'email': fields.char('Email', size=240),
@ -170,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'),
@ -183,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,
@ -199,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):
@ -210,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')]}
@ -276,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):
@ -301,6 +335,38 @@ class res_partner(osv.osv):
res.append((record.id, name))
return res
def name_create(self, cr, uid, name, context=None):
""" Override of orm's name_create method for partners. The purpose is
to handle some basic formats to create partners using the
name_create.
Supported syntax:
- 'raoul@grosbedon.fr': create a partner with name raoul@grosbedon.fr
and sets its email to raoul@grosbedon.fr
- 'Raoul Grosbedon <raoul@grosbedon.fr>': create a partner with name
Raoul Grosbedon, and set its email to raoul@grosbedon.fr
- anything else: fall back on the default name_create
Regex :
- ([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8}): raoul@grosbedon.fr
- ([\w\s.\\-]+)[\<]([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]:
Raoul Grosbedon, raoul@grosbedon.fr
"""
contact_regex = re.compile('([\w\s.\\-]+)[\<]([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]')
email_regex = re.compile('([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})')
contact_regex_res = contact_regex.findall(name)
email_regex_res = email_regex.findall(name)
if contact_regex_res:
name = contact_regex_res[0][0].rstrip(' ') # remove extra spaces on the right
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_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:
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:
args = []

View File

@ -1,16 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem icon="terp-partner" id="menu_base_partner" name="Sales" sequence="0" groups="base.group_sale_salesman"/>
<!-- Top menu item -->
<menuitem name="Sales"
id="menu_base_partner"
groups="base.group_sale_salesman"
sequence="30"/>
<menuitem id="base.menu_sales" parent="base.menu_base_partner" name="Sales" sequence="1" />
<menuitem id="menu_base_config" parent="menu_base_partner" name="Configuration" sequence="30" groups="group_system"/>
<menuitem id="menu_config_address_book" parent="menu_base_config" name="Address Book" sequence="40" groups="group_system"/>
<!--
=======================
Partner Titles
=======================
-->
<!-- Partner Titles -->
<record id="view_partner_title_tree" model="ir.ui.view">
<field name="name">res.partner.title.tree</field>
<field name="model">res.partner.title</field>
@ -31,7 +33,6 @@
<group col="4">
<field name="name"/>
<field name="shortcut"/>
<field name="domain"/>
</group>
</form>
</field>
@ -70,11 +71,8 @@
</record>
<menuitem action="action_partner_title_contact" id="menu_partner_title_contact" name="Contact Titles" parent="menu_config_address_book" sequence="3" groups="base.group_no_one"/>
<!--
=======================
Partner
=======================
-->
<!-- Partner -->
<record id="view_partner_tree" model="ir.ui.view">
<field name="name">res.partner.tree</field>
<field name="model">res.partner</field>
@ -101,25 +99,26 @@
<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" string="Customer Name"/>
(<field name="is_company" on_change="onchange_type(is_company)" class="oe_inline"/> <label for="is_company" string="Is a Company?"/>)
<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"/>
<field name="name" default_focus="1" placeholder="Name" />
</h1>
<field name="category_id" widget="many2many_tags" placeholder="Select Tags"/>
<field name="parent_id"
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)"/>
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
</div>
<div class="oe_right oe_button_box" name="buttons"> </div>
<group>
<group>
<field name="parent_id"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
attrs="{'invisible': [('is_company','=', True)]}"
on_change="onchange_address(use_parent_address, parent_id)"/>,
<label for="type" attrs="{'invisible': [('parent_id','=', False)]}"/>
<div attrs="{'invisible': [('parent_id','=', False)]}">
<field class="oe_inline"
@ -135,10 +134,10 @@
<field name="street2"/>
<div class="address_format">
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 24%%"/>
<field name="state_id" options='{"no_open": true}' placeholder="State" style="width: 24%%"/>
<field name="zip" placeholder="ZIP" style="width: 34%%"/>
</div>
<field name="country_id" placeholder="Country" class="oe_no_button"/>
<field name="country_id" placeholder="Country" options='{"no_open": true}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
</group>
@ -150,12 +149,13 @@
<field name="fax"/>
<field name="email" widget="email"/>
<field name="title" domain="[('domain', '=', 'contact')]"
class="oe_no_button" attrs="{'invisible': [('is_company','=', True)]}"/>
groups="base.group_no_one"
options='{"no_open": true}' attrs="{'invisible': [('is_company','=', True)]}" />
</group>
</group>
<notebook colspan="4">
<page string="Contacts">
<page string="Contacts" attrs="{'invisible': [('is_company','=',False)]}">
<field name="child_ids" context="{'default_parent_id': active_id}" mode="kanban">
<kanban>
<field name="color"/>
@ -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">
@ -196,7 +196,7 @@
<a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value" style="text-decoration: none;" >
<img src="/web/static/src/img/icons/terp-mail-message-new.png" border="0" width="16" height="16"/>
</a>
</td>
</tr>
</table>
@ -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>
@ -217,7 +233,7 @@
</group>
<group>
<field name="customer"/>
<field name="supplier"/>
<field name="supplier" invisible="not context.get('default_supplier')"/>
</group>
<group>
<field name="ref"/>
@ -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>
@ -247,22 +260,20 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Partner">
<group col='10' colspan='4'>
<field name="name"
filter_domain="['|','|',('name','ilike',self),('parent_id','ilike',self),('ref','=',self)]"
string="Partner"/>
<separator orientation="vertical"/>
<filter help="My Partners" icon="terp-personal+" domain="[('user_id','=',uid)]"/>
<filter string="Persons" name="type_person" icon="terp-personal" domain="[('is_company','=',0)]"/>
<filter string="Companies" name="type_company" icon="terp-partner" domain="[('is_company','=',1)]"/>
<filter string="Customers" name="customer" icon="terp-personal" domain="[('customer','=',1)]" help="Customer Partners"/>
<filter string="Suppliers" name="supplier" icon="terp-personal" domain="[('supplier','=',1)]" help="Supplier Partners"/>
<separator orientation="vertical"/>
<field name="category_id"/>
<field name="user_id"/>
<field name="parent_id" filter_domain="[('parent_id','child_of',[self])]"/>
</group>
<newline />
<field name="name"
filter_domain="['|','|',('name','ilike',self),('parent_id','ilike',self),('ref','=',self)]"
string="Partner"/>
<filter help="My Partners" icon="terp-personal+" domain="[('user_id','=',uid)]"/>
<separator/>
<filter string="Persons" name="type_person" icon="terp-personal" domain="[('is_company','=',0)]"/>
<filter string="Companies" name="type_company" icon="terp-partner" domain="[('is_company','=',1)]"/>
<separator/>
<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" 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...">
<filter string="Salesman" icon="terp-personal" domain="[]" context="{'group_by' : 'user_id'}" />
<filter string="Company" context="{'group_by': 'parent_id'}"/>
@ -288,33 +299,34 @@
<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"/>
<field name="mobile"/>
<field name="state_id"/>
<field name="category_id"/>
<templates>
<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>
<ul>
<li t-if="record.parent_id.raw_value and !record.function.raw_value"><field name="parent_id"/></li>
<li t-if="!record.parent_id.raw_value and record.function.raw_value"><field name="function"/></li>
<li t-if="record.parent_id.raw_value and record.function.raw_value"><field name="function"/> at <field name="parent_id"/></li>
<li t-if="record.city.raw_value and !record.country.raw_value"><field name="city"/></li>
<li t-if="!record.city.raw_value and record.country.raw_value"><field name="country"/></li>
<li t-if="record.city.raw_value and record.country.raw_value"><field name="city"/>, <field name="country"/></li>
<li t-if="record.mobile.raw_value"><field name="mobile"/></li>
<li t-if="record.phone.raw_value">Tel: <field name="phone"/></li>
<li t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></li>
<li t-if="record.email.raw_value"><a t-attf-href="mailto:#{record.email.raw_value}"><field name="email"/></a></li>
</ul>
<h4 class="oe_partner_heading"><a type="edit"><field name="name"/></a></h4>
<div class="oe_kanban_partner_categories"/>
<div class="oe_kanban_partner_links"/>
<ul>
<li t-if="record.parent_id.raw_value and !record.function.raw_value"><field name="parent_id"/></li>
<li t-if="!record.parent_id.raw_value and record.function.raw_value"><field name="function"/></li>
<li t-if="record.parent_id.raw_value and record.function.raw_value"><field name="function"/> at <field name="parent_id"/></li>
<li t-if="record.city.raw_value and !record.country.raw_value"><field name="city"/></li>
<li t-if="!record.city.raw_value and record.country.raw_value"><field name="country"/></li>
<li t-if="record.city.raw_value and record.country.raw_value"><field name="city"/>, <field name="country"/></li>
<li t-if="record.phone.raw_value">Tel: <field name="phone"/></li>
<li t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></li>
<li t-if="record.email.raw_value"><a t-attf-href="mailto:#{record.email.raw_value}"><field name="email"/></a></li>
</ul>
</div>
</div>
@ -332,12 +344,14 @@
<field name="view_mode">kanban,tree,form</field>
<field name="context">{"search_default_customer":1}</field>
<field name="search_view_id" ref="view_res_partner_filter"/>
<field name="help">
Click on "Create" to add a new contact in your address book.
&lt;p&gt;
A contact is either a person or a company; a person can be linked to a company as a contact of that company.
&lt;p&gt;
You will be able to follow documents and history of your contacts (invoices, meetings, projects, etc.)
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a contact in your address book.
</p><p>
OpenERP helps you easily track all activities related to
a customer; discussions, history of business opportunities,
documents, etc.
</p>
</field>
</record>
<record id="action_partner_form_view1" model="ir.actions.act_window.view">
@ -369,12 +383,14 @@
<field name="domain">[('customer','=',1)]</field>
<field name="context">{'default_customer':1, 'search_default_customer':1}</field>
<field name="filter" eval="True"/>
<field name="help">
Click on "Create" to add a new customer in your address book.
&lt;p&gt;
A contact is either a person or a company; a person can be linked to a company as a contact of that company.
&lt;p&gt;
You will be able to follow documents and history of your contacts (invoices, meetings, projects, etc.)
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a contact in your address book.
</p><p>
OpenERP helps you easily track all activities related to
a customer: discussions, history of business opportunities,
documents, etc.
</p>
</field>
</record>
@ -387,12 +403,14 @@
<field name="view_mode">kanban,tree,form</field>
<field name="context">{'search_default_supplier': 1,'default_customer': 0,'default_supplier': 1}</field>
<field name="filter" eval="True"/>
<field name="help">
Click on "Create" to add a new supplier in your address book.
&lt;p&gt;
A supplier is either a person or a company; a person can be linked to a company as a contact of that company.
&lt;p&gt;
You will be able to follow documents and history of your contacts (invoices, meetings, projects, etc.)
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a contact in your address book.
</p><p>
OpenERP helps you easily track all activities related to
a supplier: discussions, history of purchases,
documents, etc.
</p>
</field>
</record>
@ -434,11 +452,8 @@
<field name="view_type">form</field>
<field name="view_id" ref="view_payterm_form"/>
</record>
<!--
======================
Categories
======================
-->
<!-- Categories -->
<record id="view_partner_category_form" model="ir.ui.view">
<field name="name">Partner Categories</field>
<field name="model">res.partner.category</field>
@ -517,4 +532,3 @@
</data>
</openerp>

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

@ -2,6 +2,16 @@
<openerp>
<data>
<!-- res.groups -->
<record id="view_groups_search" model="ir.ui.view">
<field name="name">res.groups.search</field>
<field name="model">res.groups</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Groups">
<field name="name" filter_domain="['|', ('name','ilike',self), ('category_id','ilike',self)]" string="Group"/>
</search>
</field>
</record>
<record id="view_groups_form" model="ir.ui.view">
<field name="name">res.groups.form</field>
<field name="model">res.groups</field>
@ -81,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>
@ -191,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"/>
@ -211,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>
@ -219,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

@ -23,6 +23,10 @@
<record model="res.groups" id="group_multi_company">
<field name="name">Multi Companies</field>
</record>
<record model="res.groups" id="group_multi_currency">
<field name="name">Multi Currencies</field>
</record>
<record model="res.groups" id="group_no_one">
<field name="name">Technical Features</field>

View File

@ -117,7 +117,6 @@
"access_res_widget_user","res.widget.user","model_res_widget",,1,0,0,0
"access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0
"access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0
"access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1
"access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0
"access_ir_needaction_users_rel","ir_needaction_users_rel","model_ir_needaction_users_rel",,1,1,1,1
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
117 access_res_widget_user res.widget.user model_res_widget 1 0 0 0
118 access_ir_config_parameter ir_config_parameter model_ir_config_parameter 1 0 0 0
119 access_ir_mail_server_all ir_mail_server model_ir_mail_server 1 0 0 0
access_ir_actions_todo_category ir_actions_todo_category model_ir_actions_todo_category group_system 1 1 1 1
120 access_ir_actions_client ir_actions_client all model_ir_actions_client 1 0 0 0
121 access_ir_needaction_users_rel ir_needaction_users_rel model_ir_needaction_users_rel 1 1 1 1
122 access_ir_needaction_mixin ir_needaction_mixin model_ir_needaction_mixin 1 1 1 1

View File

@ -19,10 +19,8 @@
<rng:element name="value">
<rng:optional><rng:attribute name="model" /></rng:optional>
<rng:optional><rng:attribute name="search" /></rng:optional>
<rng:optional>
<rng:attribute name="eval"/>
</rng:optional>
<rng:empty />
<rng:optional><rng:attribute name="eval" /></rng:optional>
<rng:optional><rng:text/></rng:optional>
</rng:element>
</rng:define>

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

@ -122,6 +122,7 @@ start the server specifying the --unaccent flag.
"""
import logging
import traceback
from openerp.tools import flatten, reverse_enumerate
import fields
@ -426,6 +427,7 @@ class expression(object):
# If the field is _inherits'd, search for the working_table,
# and extract the field.
field = None
if field_path[0] in table._inherit_fields:
while True:
field = working_table._columns.get(field_path[0])
@ -474,6 +476,13 @@ class expression(object):
# the function field doesn't provide a search function and doesn't store
# values in the database, so we must ignore it : we generate a dummy leaf
self.__exp[i] = TRUE_LEAF
_logger.error(
"The field '%s' (%s) can not be searched: non-stored "
"function field without fnct_search",
field.string, left)
# avoid compiling stack trace if not needed
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug(''.join(traceback.format_stack()))
else:
subexp = field.search(cr, uid, table, left, [self.__exp[i]], context=context)
if not subexp:

View File

@ -49,7 +49,7 @@ import simplejson
_logger = logging.getLogger(__name__)
def _symbol_set(symb):
if symb == None or symb == False:
if symb is None or symb == False:
return None
elif isinstance(symb, unicode):
return symb.encode('utf-8')
@ -127,6 +127,23 @@ class _column(object):
res = obj.read(cr, uid, ids, [name], context=context)
return [x[name] for x in res]
def as_display_name(self, cr, uid, obj, value, context=None):
"""Converts a field value to a suitable string representation for a record,
e.g. when this field is used as ``rec_name``.
:param obj: the ``BaseModel`` instance this column belongs to
:param value: a proper value as returned by :py:meth:`~openerp.orm.osv.BaseModel.read`
for this column
"""
# delegated to class method, so a column type A can delegate
# to a column type B.
return self._as_display_name(self, cr, uid, obj, value, context=None)
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
# This needs to be a class method, in case a column type A as to delegate
# to a column type B.
return tools.ustr(value)
# ---------------------------------------------------------
# Simple fields
@ -154,11 +171,6 @@ class integer(_column):
def __init__(self, string='unknown', required=False, **args):
super(integer, self).__init__(string=string, required=required, **args)
if required:
_logger.debug(
"required=True is deprecated: making an integer field"
" `required` has no effect, as NULL values are "
"automatically turned into 0.")
class reference(_column):
_type = 'reference'
@ -178,11 +190,22 @@ class reference(_column):
result[value['id']] = False
return result
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
if value:
# reference fields have a 'model,id'-like value, that we need to convert
# to a real name
model_name, res_id = value.split(',')
model = obj.pool.get(model_name)
if model and res_id:
return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
return tools.ustr(value)
class char(_column):
_type = 'char'
def __init__(self, string, size, **args):
_column.__init__(self, string=string, size=size, **args)
def __init__(self, string="unknown", size=None, **args):
_column.__init__(self, string=string, size=size or None, **args)
self._symbol_set = (self._symbol_c, self._symbol_set_char)
# takes a string (encoded in utf8) and returns a string (encoded in utf8)
@ -191,7 +214,7 @@ class char(_column):
# * we need to remove the "symb==False" from the next line BUT
# for now too many things rely on this broken behavior
# * the symb==None test should be common to all data types
if symb == None or symb == False:
if symb is None or symb == False:
return None
# we need to convert the string to a unicode object to be able
@ -218,11 +241,6 @@ class float(_column):
self.digits = digits
# synopsis: digits_compute(cr) -> (precision, scale)
self.digits_compute = digits_compute
if required:
_logger.debug(
"required=True is deprecated: making a float field"
" `required` has no effect, as NULL values are "
"automatically turned into 0.0.")
def digits_change(self, cr):
if self.digits_compute:
@ -453,6 +471,11 @@ class many2one(_column):
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
return value[1] if isinstance(value, tuple) else tools.ustr(value)
class one2many(_column):
_classic_read = False
@ -543,6 +566,10 @@ class one2many(_column):
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)')
#
# Values: (0, 0, { fields }) create
@ -725,6 +752,10 @@ class many2many(_column):
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)')
def get_nice_size(value):
size = 0
@ -1078,6 +1109,12 @@ class function(_column):
if self._fnct_inv:
self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
# Function fields are supposed to emulate a basic field type,
# so they can delegate to the basic type for record name rendering
return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
# ---------------------------------------------------------
# Related fields
# ---------------------------------------------------------
@ -1211,7 +1248,6 @@ class related(function):
result[-1]['relation'] = f['relation']
self._relations = result
class sparse(function):
def convert_value(self, obj, cr, uid, record, value, read_value, context=None):

View File

@ -657,7 +657,7 @@ class BaseModel(object):
_columns = {}
_constraints = []
_defaults = {}
_rec_name = 'name'
_rec_name = None
_parent_name = 'parent_id'
_parent_store = False
_parent_order = False
@ -758,7 +758,7 @@ class BaseModel(object):
'model_id': model_id,
'model': self._name,
'name': k,
'field_description': f.string.replace("'", " "),
'field_description': f.string,
'ttype': f._type,
'relation': f._obj or '',
'view_load': (f.view_load and 1) or 0,
@ -767,7 +767,7 @@ class BaseModel(object):
'required': (f.required and 1) or 0,
'selectable': (f.selectable and 1) or 0,
'translate': (f.translate and 1) or 0,
'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
'relation_field': f._fields_id if isinstance(f, fields.one2many) else '',
'serialization_field_id': None,
}
if getattr(f, 'serialization_field', None):
@ -1054,6 +1054,13 @@ class BaseModel(object):
assert self._log_access, "TransientModels must have log_access turned on, "\
"in order to implement their access rights policy"
# Validate rec_name
if self._rec_name is not None:
assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name)
else:
self._rec_name = 'name'
def __export_row(self, cr, uid, row, fields, context=None):
if context is None:
context = {}
@ -1567,6 +1574,12 @@ class BaseModel(object):
res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
return res
def _rec_name_fallback(self, cr, uid, context=None):
rec_name = self._rec_name
if rec_name not in self._columns:
rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
return rec_name
#
# Overload this method if you need a window title which depends on the context
#
@ -1859,8 +1872,7 @@ class BaseModel(object):
return view
def _get_default_search_view(self, cr, user, context=None):
""" Generates a single-field tree view, using _rec_name if
it's one of the columns or the first column it finds otherwise
""" Generates a single-field search view, based on _rec_name.
:param cr: database cursor
:param int user: user id
@ -1868,17 +1880,12 @@ class BaseModel(object):
:returns: a tree view as an lxml document
:rtype: etree._Element
"""
_rec_name = self._rec_name
if _rec_name not in self._columns:
_rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
view = etree.Element('search', string=self._description)
etree.SubElement(view, 'field', name=_rec_name)
etree.SubElement(view, 'field', name=self._rec_name_fallback(cr, user, context))
return view
def _get_default_tree_view(self, cr, user, context=None):
""" Generates a single-field tree view, using _rec_name if
it's one of the columns or the first column it finds otherwise
""" Generates a single-field tree view, based on _rec_name.
:param cr: database cursor
:param int user: user id
@ -1886,12 +1893,8 @@ class BaseModel(object):
:returns: a tree view as an lxml document
:rtype: etree._Element
"""
_rec_name = self._rec_name
if _rec_name not in self._columns:
_rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
view = etree.Element('tree', string=self._description)
etree.SubElement(view, 'field', name=_rec_name)
etree.SubElement(view, 'field', name=self._rec_name_fallback(cr, user, context))
return view
def _get_default_calendar_view(self, cr, user, context=None):
@ -1918,7 +1921,7 @@ class BaseModel(object):
return False
view = etree.Element('calendar', string=self._description)
etree.SubElement(view, 'field', name=self._rec_name)
etree.SubElement(view, 'field', self._rec_name_fallback(cr, user, context))
if (self._date_name not in self._columns):
date_found = False
@ -2276,8 +2279,13 @@ class BaseModel(object):
return []
if isinstance(ids, (int, long)):
ids = [ids]
return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
[self._rec_name], context, load='_classic_write')]
if self._rec_name in self._all_columns:
rec_name_column = self._all_columns[self._rec_name].column
return [(r['id'], rec_name_column.as_display_name(cr, user, self, r[self._rec_name], context=context))
for r in self.read(cr, user, ids, [self._rec_name],
load='_classic_write', context=context)]
return [(id, "%s,%s" % (self._name, id)) for id in ids]
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
"""Search for records that have a display name matching the given ``name`` pattern if compared
@ -4152,6 +4160,12 @@ class BaseModel(object):
self.check_create(cr, user)
if self._log_access:
for f in LOG_ACCESS_COLUMNS:
if vals.pop(f, None) is not None:
_logger.warning(
'Field `%s` is not allowed when creating the model `%s`.',
f, self._name)
vals = self._add_missing_default_values(cr, user, vals, context)
tocreate = {}

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 *

View File

@ -142,18 +142,20 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
except Exception:
_logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True)
return ""
def _process(s, idref):
m = re.findall('[^%]%\((.*?)\)[ds]', s)
for id in m:
if not id in idref:
idref[id]=self.id_get(cr, id)
return s % idref
if t == 'xml':
def _process(s, idref):
m = re.findall('[^%]%\((.*?)\)[ds]', s)
for id in m:
if not id in idref:
idref[id]=self.id_get(cr, id)
return s % idref
_fix_multiple_roots(node)
return '<?xml version="1.0"?>\n'\
+_process("".join([etree.tostring(n, encoding='utf-8')
for n in node]),
idref)
for n in node]), idref)
if t == 'html':
return _process("".join([etree.tostring(n, encoding='utf-8')
for n in node]), idref)
if t in ('char', 'int', 'float'):
d = node.text
if t == 'int':

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.