[MERGE] merged trunk.

bzr revid: vmt@openerp.com-20120813142443-73d6zm10bqhm17mp
This commit is contained in:
Vo Minh Thu
2012-08-13 16:24:43 +02:00
38 changed files with 2645 additions and 992 deletions
+2
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',
@@ -79,6 +80,7 @@
'demo_xml': [
'base_demo.xml',
'res/res_partner_demo.xml',
'res/res_partner_demo.yml',
'res/res_widget_demo.xml',
],
'test': [
+11 -3
View File
@@ -1060,6 +1060,13 @@
<field name="company_id" eval="None"/>
<field name="customer" eval="False"/>
<field name="is_company" eval="True"/>
<field name="street">45, Rue du Palais</field>
<field name="city">Paris</field>
<field name="zip">75016</field>
<field ref="base.fr" name="country_id"/>
<field name="phone">+33 1 49 51 23 94</field>
<field name="email">info@yourcompany.com</field>
<field name="website">www.yourcompany.com</field>
</record>
<!-- Currencies -->
@@ -1081,8 +1088,9 @@
<record id="main_company" model="res.company">
<field name="name">Your Company</field>
<field name="partner_id" ref="main_partner"/>
<field name="rml_footer1">Web: www.companyname.com - Tel: +1-212-555-12345</field>
<field name="rml_footer2">IBAN: XX12 3456 7890 1234 5678 - SWIFT: SWIFTCODE - VAT: Company vat number</field>
<field name="rml_header1">Your Company Slogan</field>
<field name="rml_footer1">Web: http://www.yourcompany.com - Tel: +33 1 49 51 23 94</field>
<field name="rml_footer2">Bank Accounts... to be configured in menu Settings > Accounting/Invoicing.</field>
<field name="currency_id" ref="base.EUR"/>
</record>
@@ -1623,7 +1631,7 @@
</record>
<record id="res_bank_1" model="res.bank">
<field name="name">Reserve</field>
<field name="name">International Bank</field>
</record>
<record id="CRC" model="res.currency">
-13
View File
@@ -1,19 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="demo_address" model="res.partner">
<field name="name">Fabien D'souza</field>
<field name="street">Chaussee de Namur</field>
<field name="zip">1367</field>
<field name="city">Gerompont</field>
<field name="phone">(+32).81.81.37.00</field>
<field name="type">default</field>
<field model="res.country" name="country_id" ref="be"/>
<!-- Company ID will be set later -->
<field name="company_id" eval="None"/>
</record>
<record id="user_demo" model="res.users">
<field name="login">demo</field>
<field name="password">demo</field>
+28 -20
View File
@@ -7,14 +7,15 @@ 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-02-16 22:06+0000\n"
"Last-Translator: Davide Corio - agilebg.com <davide.corio@agilebg.com>\n"
"PO-Revision-Date: 2012-08-10 18:42+0000\n"
"Last-Translator: Leonardo Pistone - Agile BG - Domsense "
"<leonardo.pistone@domsense.com>\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:40+0000\n"
"X-Generator: Launchpad (build 15637)\n"
"X-Launchpad-Export-Date: 2012-08-11 05:45+0000\n"
"X-Generator: Launchpad (build 15780)\n"
#. module: base
#: model:res.country,name:base.sh
@@ -849,6 +850,10 @@ msgid ""
"Launch Manually Once: after hacing been launched manually, it sets "
"automatically to Done."
msgstr ""
"Manuale: Esegui manualmente\n"
"Automatico: Viene eseguito ogni volta che il sistema viene riconfigurato \n"
"Esegui manualmente una volta sola: una volta lanciata l'esecuzione, lo stato "
"viene impostato autmaticamente su \"Eseguito\""
#. module: base
#: selection:base.language.install,lang:0
@@ -1593,7 +1598,7 @@ msgstr "Login"
#: model:ir.actions.act_window,name:base.action_wizard_update_translations
#: model:ir.ui.menu,name:base.menu_wizard_update_translations
msgid "Synchronize Terms"
msgstr ""
msgstr "Sincronizza Termini"
#. module: base
#: view:ir.actions.server:0
@@ -2980,7 +2985,7 @@ msgstr "Armenia"
#: model:ir.actions.act_window,name:base.ir_property_form
#: model:ir.ui.menu,name:base.menu_ir_property_form_all
msgid "Configuration Parameters"
msgstr "Parametri Configurazione"
msgstr "Parametri di configurazione"
#. module: base
#: constraint:ir.cron:0
@@ -3077,7 +3082,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_wiki_quality_manual
msgid "Wiki: Quality Manual"
msgstr ""
msgstr "Wiki: Manuale della Qualità"
#. module: base
#: selection:ir.actions.act_window.view,view_mode:0
@@ -3105,7 +3110,7 @@ msgstr "Settore Risorse Umane"
#. module: base
#: model:ir.ui.menu,name:base.menu_dashboard_admin
msgid "Administration Dashboard"
msgstr ""
msgstr "Dashboard di Amministrazione"
#. module: base
#: code:addons/orm.py:4408
@@ -3748,7 +3753,7 @@ msgstr ""
#. module: base
#: model:ir.module.category,name:base.module_category_human_resources
msgid "Human Resources"
msgstr ""
msgstr "Risorse Umane"
#. module: base
#: model:ir.actions.act_window,name:base.action_country
@@ -3808,7 +3813,7 @@ msgstr "Partita IVA"
#. module: base
#: field:res.users,new_password:0
msgid "Set password"
msgstr ""
msgstr "Nuova password"
#. module: base
#: view:res.lang:0
@@ -4737,7 +4742,7 @@ msgstr "Kenia"
#: model:ir.actions.act_window,name:base.action_translation
#: model:ir.ui.menu,name:base.menu_action_translation
msgid "Translated Terms"
msgstr ""
msgstr "Termini Tradotti"
#. module: base
#: view:res.partner.event:0
@@ -6368,7 +6373,7 @@ msgstr "Menu"
#. module: base
#: selection:ir.actions.todo,type:0
msgid "Launch Manually Once"
msgstr ""
msgstr "Esegui manualmente una volta sola"
#. module: base
#: model:ir.module.category,name:base.module_category_hidden
@@ -6741,6 +6746,8 @@ msgid ""
"password, otherwise leave empty. After a change of password, the user has to "
"login again."
msgstr ""
"Per modificare la password scrivila qui, altrimenti lascia il campo vuoto. "
"Dopo aver cambiato la password, l'utente deve fare nuovamente login."
#. module: base
#: view:publisher_warranty.contract:0
@@ -6929,7 +6936,7 @@ msgstr ""
#. module: base
#: view:ir.property:0
msgid "Parameters that are used by all resources."
msgstr ""
msgstr "Parametri utilizzati da tutte le risorse"
#. module: base
#: model:res.country,name:base.mz
@@ -9394,7 +9401,7 @@ msgstr ""
#: model:ir.model,name:base.model_ir_model
#: model:ir.ui.menu,name:base.ir_model_model_menu
msgid "Models"
msgstr ""
msgstr "Modelli"
#. module: base
#: code:addons/base/ir/ir_cron.py:292
@@ -9405,7 +9412,7 @@ msgstr ""
#. module: base
#: selection:ir.actions.todo,type:0
msgid "Launch Manually"
msgstr ""
msgstr "Esegui manualmente"
#. module: base
#: model:res.country,name:base.be
@@ -10753,7 +10760,7 @@ msgstr "Nazione"
#. module: base
#: model:ir.ui.menu,name:base.next_id_5
msgid "Sequences & Identifiers"
msgstr ""
msgstr "Sequenze e Identificatori"
#. module: base
#: model:ir.module.module,description:base.module_l10n_th
@@ -12636,7 +12643,7 @@ msgstr "ir.values"
#. module: base
#: model:res.groups,name:base.group_no_one
msgid "Technical Features"
msgstr ""
msgstr "Funzionalità tecniche"
#. module: base
#: selection:base.language.install,lang:0
@@ -12656,7 +12663,7 @@ msgstr ""
#: view:ir.model.data:0
#: model:ir.ui.menu,name:base.ir_model_data_menu
msgid "External Identifiers"
msgstr ""
msgstr "Identificatori Esterni"
#. module: base
#: model:res.groups,name:base.group_sale_salesman
@@ -12950,6 +12957,7 @@ msgid ""
"Please define at least one SMTP server, or provide the SMTP parameters "
"explicitly."
msgstr ""
"Definire almeno un server SMTP, o fornire esplicitamente i parametri SMTP"
#. module: base
#: view:ir.attachment:0
@@ -12993,7 +13001,7 @@ msgstr "Gabon"
#. module: base
#: model:res.groups,name:base.group_multi_company
msgid "Multi Companies"
msgstr ""
msgstr "Multi-azienda"
#. module: base
#: view:ir.model:0
@@ -13166,7 +13174,7 @@ msgstr "Varie"
#: view:ir.mail_server:0
#: model:ir.ui.menu,name:base.menu_mail_servers
msgid "Outgoing Mail Servers"
msgstr ""
msgstr "Server della Posta in Uscita"
#. module: base
#: model:res.country,name:base.cn
+5 -5
View File
@@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:44+0000\n"
"PO-Revision-Date: 2012-02-19 13:01+0000\n"
"PO-Revision-Date: 2012-08-09 06:15+0000\n"
"Last-Translator: Erwin <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-07-19 04:38+0000\n"
"X-Generator: Launchpad (build 15637)\n"
"X-Launchpad-Export-Date: 2012-08-10 04:58+0000\n"
"X-Generator: Launchpad (build 15761)\n"
#. module: base
#: model:res.country,name:base.sh
@@ -1443,7 +1443,7 @@ msgstr ""
"U kunt de navolgende extra data toevoegen aan een verkooporder:\n"
"* Aanvraagdatum\n"
"* Bevestigde datum\n"
"* Effectieve datum\n"
"* Boek datum\n"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_sequence
@@ -8110,7 +8110,7 @@ msgstr "Kenmerk"
#. module: base
#: model:ir.module.module,shortdesc:base.module_analytic
msgid "Analytic Accounting"
msgstr "Kostenplaats boekhouding"
msgstr "Kostenplaatsen"
#. module: base
#: view:ir.actions.report.xml:0
+3 -3
View File
@@ -223,11 +223,11 @@ class act_window(osv.osv):
help="Model name of the object to open in the view window"),
'src_model': fields.char('Source Model', size=64,
help="Optional model name of the objects on which this action should be visible"),
'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline')], 'Target Window'),
'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
help="View type: set to 'tree' for a hierarchical tree view, or 'form' for other views"),
'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline Edit'),('inlineview','Inline View')], 'Target Window'),
'view_mode': fields.char('View Mode', size=250, required=True,
help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
help="View type: Tree type to use for the tree view, set to 'tree' for a hierarchical tree view, or 'form' for a regular list view"),
'usage': fields.char('Action Usage', size=32,
help="Used to filter menu and home actions from the user form."),
'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
+38 -19
View File
@@ -180,8 +180,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),
@@ -358,22 +358,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})
@@ -418,8 +403,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'})
@@ -429,6 +441,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)
+2 -2
View File
@@ -112,8 +112,8 @@
<h1><field name="shortdesc"/></h1>
<h2 class="oe_fade"><field name="summary"/></h2>
<button name="button_immediate_install" states="uninstalled" string="Install" type="object" class="oe_highlight"/>
<button name="button_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/>
<button name="button_uninstall" states="installed" string="Uninstall" type="object"
<button name="button_immediate_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/>
<button name="button_immediate_uninstall" states="installed" string="Uninstall" type="object"
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" type="object"/>
@@ -9,7 +9,7 @@
<form string="Import Translation" version="7.0">
<group>
<field name="name"/>
<field name="code" string="Code" placeholder="en_US"/>
<field name="code" string="Code" placeholder="e.g. en_US"/>
<field name="data"/>
<field name="overwrite"/>
</group>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="action_server_module_immediate_install" model="ir.actions.server">
<field name="name">Module Immediate Install</field>
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_ir_module_module" />
<field name="state">code</field>
<field name="code">self.button_immediate_install(cr, uid, context.get('active_ids', []), context=context)</field>
</record>
<record model="ir.values" id="action_module_immediate_install">
<field name="name">action_module_immediate_install</field>
<field name="action_id" ref="action_server_module_immediate_install" />
<field name="value" eval="'ir.actions.server,' + str(ref('action_server_module_immediate_install'))" />
<field name="key">action</field>
<field name="model_id" ref="model_ir_module_module" />
<field name="model">ir.module.module</field>
<field name="key2">client_action_multi</field>
</record>
</data>
</openerp>
+2 -2
View File
@@ -105,8 +105,8 @@
<field name="zip" class="oe_inline" placeholder="ZIP"/>
<field name="city" class="oe_inline" placeholder="City"/>
</div>
<field name="state_id" placeholder="State"/>
<field name="country_id" placeholder="Country"/>
<field name="state_id" placeholder="State" options='{"no_open": true}'/>
<field name="country_id" placeholder="Country" options='{"no_open": true}'/>
</div>
</group>
<group name="bank" string="Information About the Bank">
+1 -1
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'),
+3 -3
View File
@@ -33,7 +33,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">
@@ -50,10 +50,10 @@
<field name="street2"/>
<div>
<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" class="oe_no_button" placeholder="State" style="width: 24%%" options='{"no_open": true}'/>
<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" class="oe_no_button" options='{"no_open": true}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
</group>
+1 -1
View File
@@ -67,7 +67,7 @@
<group>
<field name="name"/>
<field name="code"/>
<field name="country_id"/>
<field name="country_id" options='{"no_open": true}'/>
</group>
</form>
</field>
+45 -12
View File
@@ -133,6 +133,15 @@ class res_partner(osv.osv):
res[partner.id] =self._display_address(cr, uid, partner, context=context)
return res
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = tools.image_get_resized_images(obj.image)
return result
def _set_image(self, cr, uid, id, name, value, args, context=None):
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
_order = "name"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
@@ -174,7 +183,26 @@ class res_partner(osv.osv):
'birthdate': fields.char('Birthdate', size=64),
'is_company': fields.boolean('Company', help="Check if the contact is a company, otherwise it is a person"),
'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"),
'photo': fields.binary('Photo'),
'image': fields.binary("Image",
help="This field holds the image used as avatar for the "\
"partner. The image is base64 encoded, and PIL-supported. "\
"It is limited to a 1024x1024 px image."),
'image_medium': fields.function(_get_image, fnct_inv=_set_image,
string="Medium-sized image", type="binary", multi="_get_image",
store = {
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
},
help="Medium-sized image of the partner. It is automatically "\
"resized as a 180x180 px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Small-sized image", type="binary", multi="_get_image",
store = {
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
},
help="Small-sized image of the partner. It is automatically "\
"resized as a 50x50 px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required."),
'company_id': fields.many2one('res.company', 'Company', select=1),
'color': fields.integer('Color Index'),
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
@@ -187,12 +215,12 @@ class res_partner(osv.osv):
return [context['category_id']]
return False
def _get_photo(self, cr, uid, is_company, context=None):
def _get_default_image(self, cr, uid, is_company, context=None):
if is_company:
path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'company_icon.png')
image_path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'company_icon.png')
else:
path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'photo.png')
return open(path, 'rb').read().encode('base64')
image_path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'photo.png')
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
_defaults = {
'active': True,
@@ -203,7 +231,9 @@ class res_partner(osv.osv):
'is_company': False,
'type': 'default',
'use_parent_address': True,
'photo': lambda self, cr, uid, context: self._get_photo(cr, uid, False, context),
'image': lambda self, cr, uid, context: self._get_default_image(cr, uid, False, context),
'image_small': lambda self, cr, uid, context: self._get_default_image(cr, uid, False, context),
'image_medium': lambda self, cr, uid, context: self._get_default_image(cr, uid, False, context),
}
def copy(self, cr, uid, id, default=None, context=None):
@@ -214,8 +244,9 @@ class res_partner(osv.osv):
return super(res_partner, self).copy(cr, uid, id, default, context)
def onchange_type(self, cr, uid, ids, is_company, context=None):
value = {'title': False,
'photo': self._get_photo(cr, uid, is_company, context)}
# get value as for an onchange on the image
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True)
value['title'] = False
if is_company:
value['parent_id'] = False
domain = {'title': [('domain', '=', 'partner')]}
@@ -280,8 +311,9 @@ class res_partner(osv.osv):
domain_siblings = [('parent_id', '=', vals['parent_id']), ('use_parent_address', '=', True)]
update_ids = [vals['parent_id']] + self.search(cr, uid, domain_siblings, context=context)
self.update_address(cr, uid, update_ids, vals, context)
if 'photo' not in vals :
vals['photo'] = self._get_photo(cr, uid, vals.get('is_company', False) or context.get('default_is_company'), context)
if 'image' not in vals :
image_value = self._get_default_image(cr, uid, vals.get('is_company', False) or context.get('default_is_company'), context)
vals.update(tools.image_get_resized_images(image_value, return_big=True))
return super(res_partner,self).create(cr, uid, vals, context=context)
def update_address(self, cr, uid, ids, vals, context=None):
@@ -329,12 +361,13 @@ class res_partner(osv.osv):
email = contact_regex_res[0][1]
rec_id = self.create(cr, uid, {self._rec_name: name, 'email': email}, context);
return self.name_get(cr, uid, [rec_id], context)[0]
elif email_regex:
elif email_regex_res:
email = '%s' % (email_regex_res[0])
rec_id = self.create(cr, uid, {self._rec_name: email, 'email': email}, context);
return self.name_get(cr, uid, [rec_id], context)[0]
else:
return super(res_partner, self).create(cr, uid, name, context)
rec_id = super(res_partner, self).create(cr, uid, {self._rec_name: name}, context)
return self.name_get(cr, uid, [rec_id], context)[0]
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
if not args:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,261 @@
-
!record {model: 'res.partner', id: base.res_partner_address_1}:
name: Tang Tsui
parent_id: base.res_partner_1
use_parent_address: True
function: Service Manager
email: tang@asustek.com
-
!record {model: 'res.partner', id: base.res_partner_address_2}:
name: Joseph Walters
parent_id: base.res_partner_1
use_parent_address: True
function: Store Manager
email: joseph.walters@asustek.com
-
!record {model: 'res.partner', id: base.res_partner_address_3}:
name: Thomas Passot
parent_id: base.res_partner_2
use_parent_address: True
function: Functional Consultant
email: p.thomas@agrolait.com
-
!record {model: 'res.partner', id: base.res_partner_address_4}:
name: Michel Fletcher
parent_id: base.res_partner_2
use_parent_address: True
function: Analyst
email: m.fletcher@agrolait.com
-
!record {model: 'res.partner', id: base.res_partner_address_5}:
name: Chao Wang
parent_id: base.res_partner_3
use_parent_address: True
function: Marketing Manager
email: chao_wang@chinaexport.com
-
!record {model: 'res.partner', id: base.res_partner_address_6}:
name: Zhi Ch'ang
parent_id: base.res_partner_3
use_parent_address: True
function: Supervisor
email: zhi_chang@chinaexport.com
-
!record {model: 'res.partner', id: base.res_partner_address_7}:
name: Richard Ellis
parent_id: base.res_partner_4
use_parent_address: True
function: Production Supervisor
email:
-
!record {model: 'res.partner', id: base.res_partner_address_8}:
name: Paul Williams
parent_id: base.res_partner_4
use_parent_address: True
function: Line Mechanic
email:
-
!record {model: 'res.partner', id: base.res_partner_address_9}:
name: Brian Williams
parent_id: base.res_partner_4
use_parent_address: True
function: Computer Technician
email:
-
!record {model: 'res.partner', id: base.res_partner_address_10}:
name: David Simpson
parent_id: base.res_partner_5
use_parent_address: True
function: Senior Consultant
email: david.s@tech.info
-
!record {model: 'res.partner', id: base.res_partner_address_11}:
name: John M. Brown
parent_id: base.res_partner_5
use_parent_address: True
function: Director
email: john.b@tech.info
-
!record {model: 'res.partner', id: base.res_partner_address_12}:
name: James Miller
parent_id: base.res_partner_6
use_parent_address: True
function: Electrical Supervisor
email:
-
!record {model: 'res.partner', id: base.res_partner_address_13}:
name: Charlie Bernard
parent_id: base.res_partner_7
use_parent_address: True
function: Senior Associate
email: charlie.bernard@wealthyandsons.com
-
!record {model: 'res.partner', id: base.res_partner_address_14}:
name: Jessica Dupont
parent_id: base.res_partner_7
use_parent_address: True
function: Analyst
email: jessica.dupont@wealthyandsons.com
-
!record {model: 'res.partner', id: base.res_partner_address_15}:
name: Phillipp Miller
parent_id: base.res_partner_8
use_parent_address: True
function: Creative Director
email:
-
!record {model: 'res.partner', id: base.res_partner_address_16}:
name: Ayaan Agarwal
parent_id: base.res_partner_9
use_parent_address: True
function: Director
email:
-
!record {model: 'res.partner', id: base.res_partner_address_17}:
name: Daniel Jackson
parent_id: base.res_partner_10
use_parent_address: True
function: Managing Partner
email: daniel@jackson.com
-
!record {model: 'res.partner', id: base.res_partner_address_18}:
name: William Thomas
parent_id: base.res_partner_10
use_parent_address: True
function: Senior Consultant
email: william@jackson.com
-
!record {model: 'res.partner', id: base.res_partner_address_19}:
name: Sergio Pérez
parent_id: base.res_partner_11
use_parent_address: True
function: Accountant
email:
-
!record {model: 'res.partner', id: base.res_partner_address_20}:
name: Laura Castro
parent_id: base.res_partner_11
use_parent_address: True
function: Goods Supervisor
email:
-
!record {model: 'res.partner', id: base.res_partner_address_21}:
name: Luc Maurer
parent_id: base.res_partner_12
use_parent_address: True
function: Director
email:
-
!record {model: 'res.partner', id: base.res_partner_address_22}:
name: Laith Jubair
parent_id: base.res_partner_13
use_parent_address: True
function: Director
email:
-
!record {model: 'res.partner', id: base.res_partner_address_23}:
name: Angel Cook
parent_id: base.res_partner_14
use_parent_address: True
function: General Manager
email: angel.cook@chamberworks.com
-
!record {model: 'res.partner', id: base.res_partner_address_24}:
name: Robert Anderson
parent_id: base.res_partner_14
use_parent_address: True
function: System Analyst
email: robert.anderson@chamberworks.com
-
!record {model: 'res.partner', id: base.res_partner_address_25}:
name: Jacob Taylor
parent_id: base.res_partner_15
use_parent_address: True
function: Order Clerk
email:
-
!record {model: 'res.partner', id: base.res_partner_address_26}:
name: Arthur Gomez
parent_id: base.res_partner_16
use_parent_address: True
function: Software Developer
email:
-
!record {model: 'res.partner', id: base.res_partner_address_27}:
name: Julia Rivero
parent_id: base.res_partner_16
use_parent_address: True
function: Technical Director
email:
-
!record {model: 'res.partner', id: base.res_partner_address_28}:
name: Benjamin Flores
parent_id: base.res_partner_17
use_parent_address: True
function: Business Executive
email: ben@nebula.ar
-
!record {model: 'res.partner', id: base.res_partner_address_29}:
name: George Wilson
parent_id: base.res_partner_18
use_parent_address: True
function: Chief Information Officer (CIO)
email:
-
!record {model: 'res.partner', id: base.res_partner_address_30}:
name: Lucas Jones
parent_id: base.res_partner_18
use_parent_address: True
function: Functional Consultant
email: jones@thinkbig.com
-
!record {model: 'res.partner', id: base.res_partner_address_31}:
name: Edward Foster
parent_id: base.res_partner_19
use_parent_address: True
function: Sales Representative
email: efoster@seagate.com
-
!record {model: 'res.partner', id: base.res_partner_address_32}:
name: Robin Smith
parent_id: base.res_partner_21
use_parent_address: True
function: Sales Manager
email:
-
!record {model: 'res.partner', id: base.res_partner_address_33}:
name: Morgan Rose
parent_id: base.res_partner_21
use_parent_address: True
function: Financial Manager
email:
-
!record {model: 'res.partner', id: base.res_partner_address_34}:
name: Kevin Clarke
parent_id: base.res_partner_21
use_parent_address: True
function: Knowledge Manager
email:
-
!record {model: 'res.partner', id: base.res_partner_address_35}:
name: Peter Mitchell
parent_id: base.res_partner_22
use_parent_address: True
function: Store Manager
email:
-
!record {model: 'res.partner', id: base.res_partner_main1}:
name: Mark Davis
customer: False
parent_id: base.main_partner
use_parent_address: True
function: Chief Executive Officer (CEO)
email: mark@yourcompany.com
-
!record {model: 'res.partner', id: base.res_partner_main2}:
name: Roger Scott
customer: False
parent_id: base.main_partner
use_parent_address: True
function: Chief Operations Officer (COO)
email: roger@yourcompany.com
+35 -20
View File
@@ -95,27 +95,28 @@
<field name="arch" type="xml">
<form string="Partners" version="7.0">
<sheet>
<field name="photo" widget='image' class="oe_avatar oe_left"/>
<field name="image_small" widget='image' class="oe_avatar oe_left"/>
<div class="oe_title">
<div class="oe_edit_only">
<label for="name"/> (
<field name="is_company" on_change="onchange_type(is_company)" class="oe_inline"/> <label for="is_company" string="Is a Company?"/>)
</div>
<h1>
<field name="name" default_focus="1" placeholder="Name" />
</h1>
<field name="parent_id"
string="Work for Company"
placeholder="Company"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
attrs="{'invisible': [('is_company','=', True)]}"
on_change="onchange_address(use_parent_address, parent_id)"/>
<div class="oe_edit_only">
<field name="is_company" on_change="onchange_type(is_company)" class="oe_inline"/> <label for="is_company" string="Company?"/>
<field name="category_id" widget="many2many_tags" placeholder="Select Tags"/>
</div>
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
</div>
<div class="oe_right oe_button_box" name="buttons"> </div>
<group>
<group>
<label for="type" attrs="{'invisible': [('parent_id','=', False)]}"/>
<div attrs="{'invisible': [('parent_id','=', False)]}">
<label for="type" attrs="{'invisible': [('parent_id','=', False)]}" invisible="1"/>
<div attrs="{'invisible': [('parent_id','=', False)]}" invisible="1" name="div_type">
<field class="oe_inline"
name="type"/>
<label for="use_parent_address" class="oe_edit_only"/>
@@ -129,10 +130,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" class="oe_no_button" placeholder="State" style="width: 24%%" options='{"no_open": true}'/>
<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" class="oe_no_button" options='{"no_open": true}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
</group>
@@ -144,6 +145,7 @@
<field name="fax"/>
<field name="email" widget="email"/>
<field name="title" domain="[('domain', '=', 'contact')]"
groups="base.group_no_one"
options='{"no_open": true}' attrs="{'invisible': [('is_company','=', True)]}" />
</group>
</group>
@@ -162,7 +164,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"/>
@@ -175,7 +177,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">
@@ -201,17 +203,33 @@
</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>
<field name="user_id" />
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
<group>
<field name="customer"/>
<field name="supplier" invisible="1"/>
<field name="supplier" invisible="not context.get('default_supplier')"/>
</group>
<group>
<field name="ref"/>
@@ -226,9 +244,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>
@@ -251,7 +266,7 @@
<filter string="Customers" name="customer" icon="terp-personal" domain="[('customer','=',1)]" help="Customer Partners"/>
<separator/>
<filter string="Suppliers" name="supplier" icon="terp-personal" domain="[('supplier','=',1)]" help="Supplier Partners"/>
<field name="category_id"/>
<field name="category_id" string="Category" filter_domain="[('category_id','ilike', self)]"/>
<field name="user_id"/>
<field name="parent_id" filter_domain="[('parent_id','child_of',[self])]"/>
<group expand="0" string="Group By...">
@@ -278,7 +293,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"/>
@@ -289,7 +304,7 @@
<t t-name="kanban-box">
<div class="oe_kanban_vignette">
<a type="edit">
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" class="oe_kanban_image"/>
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.value)" class="oe_kanban_image"/>
</a>
<div class="oe_kanban_details">
<h4 class="oe_partner_heading"><a type="edit"><field name="name"/></a></h4>
+49 -39
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,18 @@ class users(osv.osv):
pass
return result
def _get_avatar(self, cr, uid, context=None):
# default avatar file name: avatar0 -> avatar6.png, choose randomly
avatar_path = openerp.modules.get_module_resource('base', 'static/src/img', 'avatar%d.png' % random.randint(0, 6))
return self._avatar_resize(cr, uid, open(avatar_path, 'rb').read().encode('base64'), context=context)
def _get_default_image(self, cr, uid, context=None):
# default image file name: avatar0 -> avatar6.png, choose randomly
image_path = openerp.modules.get_module_resource('base', 'static/src/img', 'avatar%d.png' % random.randint(0, 6))
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
_defaults = {
'password' : '',
'context_lang': lambda self, cr, uid, context: context.get('lang', 'en_US'),
'context_tz': lambda self, cr, uid, context: context.get('tz', False),
'avatar': _get_avatar,
'image': _get_default_image,
'image_small': _get_default_image,
'image_medium': _get_default_image,
'active' : True,
'menu_id': _get_menu,
'company_id': _get_company,
@@ -338,7 +335,7 @@ class users(osv.osv):
}
# User can write to a few of her own fields (but not her groups for example)
SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'user_email', 'name', 'avatar', 'avatar_big']
SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'user_email', 'name', 'image', 'image_medium', 'image_small']
def write(self, cr, uid, ids, values, context=None):
if not hasattr(ids, '__iter__'):
@@ -539,6 +536,19 @@ class users(osv.osv):
return self.write(cr, uid, uid, {'password': new_passwd})
raise osv.except_osv(_('Warning!'), _("Setting empty passwords is not allowed for security reasons!"))
def preference_save(self, cr, uid, ids, context=None):
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def preference_change_password(self, cr, uid, ids, context=None):
return {
'type': 'ir.actions.client',
'tag': 'change_password',
'target': 'new',
}
def has_group(self, cr, uid, group_ext_id):
"""Checks whether user belongs to given group.
+9 -11
View File
@@ -88,7 +88,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>
@@ -195,9 +195,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"/>
@@ -215,7 +214,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>
@@ -223,14 +226,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"/>
+1 -1
View File
@@ -7,7 +7,7 @@
-->
<assert model="res.request" id="res_partner_asustek_request" string="Reference field request returns a browsable object">
<test expr="ref_doc1.name">ASUStek</test>
<test expr="ref_doc1.name">ASUSTeK</test>
</assert>
</data>
@@ -9,14 +9,14 @@
Test hierarchical search in M2M with child ID1
-
!python {model: res.partner }: |
ids = self.search(cr, uid, [('category_id', 'child_of','Components Supplier')])
ids = self.search(cr, uid, [('category_id', 'child_of','Customer')])
assert len(ids) >= 1, ids
-
Test hierarchical search in M2M with child ID2
-
!python {model: res.partner }: |
ids = self.search(cr, uid, [('category_id', 'child_of','Miscellaneous Suppliers')])
assert len(ids) >= 1, ids
ids = self.search(cr, uid, [('category_id', 'child_of','Manufacturer')])
assert len(ids) == 2, ids
-
"1.0 Setup test partner categories: parent root"
@@ -464,8 +464,8 @@
!python {model: res.partner }: |
all_ids = self.search(cr, uid, [('name', '=like', 'A_e_or')])
assert len(all_ids) == 1, "Must match one partner (Axelor), got %r"%all_ids
all_ids = self.search(cr, uid, [('name', '=ilike', 'm_____')])
assert len(all_ids) == 1, "Must match *only* one partner (Maxtor), got %r"%all_ids
all_ids = self.search(cr, uid, [('name', '=ilike', 'v%')])
assert len(all_ids) >= 1, "Must match one partner (Vicking Direct), got %r"%all_ids
-
Check that =like/=ilike expressions (no wildcard variants of like/ilike) are working on translated field.
-
@@ -512,7 +512,7 @@
Testing for Many2Many field with category supplier and active=False
-
!python {model: res.partner }: |
vals = {'category_id': [(6, 0, [ref("base.res_partner_category_8")])],
vals = {'category_id': [(6, 0, [ref("base.res_partner_category_1")])],
'name': 'OpenERP Test',
'active': False,
'child_ids': [(0, 0, {'name': 'address of OpenERP Test', 'country_id': ref("base.be")})]
+1 -5
View File
@@ -24,11 +24,7 @@
"""
import openerp.modules.db
import openerp.modules.graph
import openerp.modules.loading
import openerp.modules.migration
import openerp.modules.module
from . import db, graph, loading, migration, module, registry
# TODO temporarily expose those things
from openerp.modules.module import \
+14 -5
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.
-10
View File
@@ -171,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'
@@ -246,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:
+5 -7
View File
@@ -2442,9 +2442,9 @@ class BaseModel(object):
context=context)
result_template = dict.fromkeys(aggregated_fields, False)
result_template.update({groupby + '_count':0})
result_template[groupby + '_count'] = 0
if groupby_list and len(groupby_list) > 1:
result_template.update(__context={'group_by': groupby_list[1:]})
result_template['__context'] = {'group_by': groupby_list[1:]}
# Merge the left_side (current results as dicts) with the right_side (all
# possible values as m2o pairs). Both lists are supposed to be using the
@@ -2463,10 +2463,8 @@ class BaseModel(object):
grouped_value = right_side[0]
if not grouped_value in known_values:
line = dict(result_template)
line.update({
groupby: right_side,
'__domain': [(groupby,'=',grouped_value)] + domain,
})
line[groupby] = right_side
line['__domain'] = [(groupby,'=',grouped_value)] + domain
result.append(line)
known_values[grouped_value] = line
while read_group_result or all_groups:
@@ -4197,7 +4195,7 @@ class BaseModel(object):
cr.execute("SELECT nextval('"+self._sequence+"')")
except:
raise except_orm(_('UserError'),
_('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
_('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
id_new = cr.fetchone()[0]
for table in tocreate:
+4 -7
View File
@@ -8,20 +8,17 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not.
See the :ref:`test-framework` section in the :ref:`features` list.
"""
import test_expression
import test_ir_sequence
import test_orm
import test_view_validation
import test_uninstall
from . import test_expression, test_ir_sequence, test_orm,\
test_view_validation, test_uninstall
fast_suite = [
test_ir_sequence,
]
]
checks = [
test_expression,
test_orm,
test_view_validation,
]
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -50,7 +50,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"/>
@@ -0,0 +1 @@
import models
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
{
'name': 'test-import-export',
'version': '0.1',
'category': 'Tests',
'description': """A module to test import/export.""",
'author': 'OpenERP SA',
'maintainer': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
'data': [],
'installable': True,
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+118
View File
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from openerp.osv import orm, fields
def selection_fn(obj, cr, uid, context=None):
return list(enumerate(["Corge", "Grault", "Wheee", "Moog"]))
def function_fn(model, cr, uid, ids, field_name, arg, context):
return dict((id, 3) for id in ids)
def function_fn_write(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context):
""" just so CreatorCase.export can be used
"""
pass
models = [
('boolean', fields.boolean()),
('integer', fields.integer()),
('float', fields.float()),
('decimal', fields.float(digits=(16, 3))),
('string.bounded', fields.char('unknown', size=16)),
('string', fields.char('unknown', size=None)),
('date', fields.date()),
('datetime', fields.datetime()),
('text', fields.text()),
('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])),
('selection.function', fields.selection(selection_fn)),
# just relate to an integer
('many2one', fields.many2one('export.integer')),
('one2many', fields.one2many('export.one2many.child', 'parent_id')),
('many2many', fields.many2many('export.many2many.other')),
('function', fields.function(function_fn, fnct_inv=function_fn_write, type="integer")),
# related: specialization of fields.function, should work the same way
# TODO: reference
]
for name, field in models:
attrs = {
'_name': 'export.%s' % name,
'_columns': {
'const': fields.integer(),
'value': field
},
'_defaults': {'const': 4},
'name_get': (lambda self, cr, uid, ids, context=None:
[(record.id, "%s:%s" % (self._name, record.value))
for record in self.browse(cr, uid, ids, context=context)]),
'name_search': (lambda self, cr, uid, name, operator, context=None:
self.name_get(cr, uid,
self.search(cr, uid, [['value', operator, int(name.split(':')[1])]])
, context=context)
if isinstance(name, basestring) and name.split(':')[0] == self._name
else [])
}
NewModel = type(
'Export%s' % ''.join(section.capitalize() for section in name.split('.')),
(orm.Model,),
attrs)
class One2ManyChild(orm.Model):
_name = 'export.one2many.child'
# FIXME: orm.py:1161, fix to name_get on m2o field
_rec_name = 'value'
_columns = {
'parent_id': fields.many2one('export.one2many'),
'str': fields.char('unknown', size=None),
'value': fields.integer()
}
def name_get(self, cr, uid, ids, context=None):
return [(record.id, "%s:%s" % (self._name, record.value))
for record in self.browse(cr, uid, ids, context=context)]
class One2ManyMultiple(orm.Model):
_name = 'export.one2many.multiple'
_columns = {
'const': fields.integer(),
'child1': fields.one2many('export.one2many.child.1', 'parent_id'),
'child2': fields.one2many('export.one2many.child.2', 'parent_id'),
}
_defaults = { 'const': 36 }
class One2ManyChildMultiple(orm.Model):
_name = 'export.one2many.multiple.child'
# FIXME: orm.py:1161, fix to name_get on m2o field
_rec_name = 'value'
_columns = {
'parent_id': fields.many2one('export.one2many.multiple'),
'str': fields.char('unknown', size=None),
'value': fields.integer()
}
def name_get(self, cr, uid, ids, context=None):
return [(record.id, "%s:%s" % (self._name, record.value))
for record in self.browse(cr, uid, ids, context=context)]
class One2ManyChild1(orm.Model):
_name = 'export.one2many.child.1'
_inherit = 'export.one2many.multiple.child'
class One2ManyChild2(orm.Model):
_name = 'export.one2many.child.2'
_inherit = 'export.one2many.multiple.child'
class Many2ManyChild(orm.Model):
_name = 'export.many2many.other'
# FIXME: orm.py:1161, fix to name_get on m2o field
_rec_name = 'value'
_columns = {
'str': fields.char('unknown', size=None),
'value': fields.integer()
}
def name_get(self, cr, uid, ids, context=None):
return [(record.id, "%s:%s" % (self._name, record.value))
for record in self.browse(cr, uid, ids, context=context)]
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
return (self.name_get(cr, user,
self.search(cr, user, [['value', operator, int(name.split(':')[1])]])
, context=context)
if isinstance(name, basestring) and name.split(':')[0] == self._name
else [])
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from . import test_export, test_import
fast_suite = [
]
checks = [
test_export,
test_import,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,589 @@
# -*- coding: utf-8 -*-
import itertools
import openerp.modules.registry
import openerp
from openerp.tests import common
class CreatorCase(common.TransactionCase):
model_name = False
def __init__(self, *args, **kwargs):
super(CreatorCase, self).__init__(*args, **kwargs)
self.model = None
def setUp(self):
super(CreatorCase, self).setUp()
self.model = self.registry(self.model_name)
def make(self, value):
id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': value})
return self.model.browse(self.cr, openerp.SUPERUSER_ID, [id])[0]
def export(self, value, fields=('value',), context=None):
record = self.make(value)
return self.model._BaseModel__export_row(
self.cr, openerp.SUPERUSER_ID, record,
[f.split('/') for f in fields],
context=context)
class test_boolean_field(CreatorCase):
model_name = 'export.boolean'
def test_true(self):
self.assertEqual(
self.export(True),
[[u'True']])
def test_false(self):
""" ``False`` value to boolean fields is unique in being exported as a
(unicode) string, not a boolean
"""
self.assertEqual(
self.export(False),
[[u'False']])
class test_integer_field(CreatorCase):
model_name = 'export.integer'
def test_empty(self):
self.assertEqual(self.model.search(self.cr, openerp.SUPERUSER_ID, []), [],
"Test model should have no records")
def test_0(self):
self.assertEqual(
self.export(0),
[[False]])
def test_basic_value(self):
self.assertEqual(
self.export(42),
[[u'42']])
def test_negative(self):
self.assertEqual(
self.export(-32),
[[u'-32']])
def test_huge(self):
self.assertEqual(
self.export(2**31-1),
[[unicode(2**31-1)]])
class test_float_field(CreatorCase):
model_name = 'export.float'
def test_0(self):
self.assertEqual(
self.export(0.0),
[[False]])
def test_epsilon(self):
self.assertEqual(
self.export(0.000000000027),
[[u'2.7e-11']])
def test_negative(self):
self.assertEqual(
self.export(-2.42),
[[u'-2.42']])
def test_positive(self):
self.assertEqual(
self.export(47.36),
[[u'47.36']])
def test_big(self):
self.assertEqual(
self.export(87654321.4678),
[[u'87654321.4678']])
class test_decimal_field(CreatorCase):
model_name = 'export.decimal'
def test_0(self):
self.assertEqual(
self.export(0.0),
[[False]])
def test_epsilon(self):
""" epsilon gets sliced to 0 due to precision
"""
self.assertEqual(
self.export(0.000000000027),
[[False]])
def test_negative(self):
self.assertEqual(
self.export(-2.42),
[[u'-2.42']])
def test_positive(self):
self.assertEqual(
self.export(47.36),
[[u'47.36']])
def test_big(self):
self.assertEqual(
self.export(87654321.4678), [[u'87654321.468']])
class test_string_field(CreatorCase):
model_name = 'export.string.bounded'
def test_empty(self):
self.assertEqual(
self.export(""),
[[False]])
def test_within_bounds(self):
self.assertEqual(
self.export("foobar"),
[[u"foobar"]])
def test_out_of_bounds(self):
self.assertEqual(
self.export("C for Sinking, "
"Java for Drinking, "
"Smalltalk for Thinking. "
"...and Power to the Penguin!"),
[[u"C for Sinking, J"]])
class test_unbound_string_field(CreatorCase):
model_name = 'export.string'
def test_empty(self):
self.assertEqual(
self.export(""),
[[False]])
def test_small(self):
self.assertEqual(
self.export("foobar"),
[[u"foobar"]])
def test_big(self):
self.assertEqual(
self.export("We flew down weekly to meet with IBM, but they "
"thought the way to measure software was the amount "
"of code we wrote, when really the better the "
"software, the fewer lines of code."),
[[u"We flew down weekly to meet with IBM, but they thought the "
u"way to measure software was the amount of code we wrote, "
u"when really the better the software, the fewer lines of "
u"code."]])
class test_text(CreatorCase):
model_name = 'export.text'
def test_empty(self):
self.assertEqual(
self.export(""),
[[False]])
def test_small(self):
self.assertEqual(
self.export("foobar"),
[[u"foobar"]])
def test_big(self):
self.assertEqual(
self.export("So, `bind' is `let' and monadic programming is"
" equivalent to programming in the A-normal form. That"
" is indeed all there is to monads"),
[[u"So, `bind' is `let' and monadic programming is equivalent to"
u" programming in the A-normal form. That is indeed all there"
u" is to monads"]])
class test_date(CreatorCase):
model_name = 'export.date'
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_basic(self):
self.assertEqual(
self.export('2011-11-07'),
[[u'2011-11-07']])
class test_datetime(CreatorCase):
model_name = 'export.datetime'
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_basic(self):
self.assertEqual(
self.export('2011-11-07 21:05:48'),
[[u'2011-11-07 21:05:48']])
def test_tz(self):
""" Export ignores the timezone and always exports to UTC
.. note:: on the other hand, export uses user lang for name_get
"""
# NOTE: ignores user timezone, always exports to UTC
self.assertEqual(
self.export('2011-11-07 21:05:48', context={'tz': 'Pacific/Norfolk'}),
[[u'2011-11-07 21:05:48']])
class test_selection(CreatorCase):
model_name = 'export.selection'
translations_fr = [
("Qux", "toto"),
("Bar", "titi"),
("Foo", "tete"),
]
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_value(self):
""" selections export the *label* for their value
"""
self.assertEqual(
self.export(2),
[[u"Bar"]])
def test_localized_export(self):
self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
'name': u'Français',
'code': 'fr_FR',
'translatable': True,
'date_format': '%d.%m.%Y',
'decimal_point': ',',
'thousand_sep': ' ',
})
Translations = self.registry('ir.translation')
for source, value in self.translations_fr:
Translations.create(self.cr, openerp.SUPERUSER_ID, {
'name': 'export.selection,value',
'lang': 'fr_FR',
'type': 'selection',
'src': source,
'value': value
})
self.assertEqual(
self.export(2, context={'lang': 'fr_FR'}),
[[u'Bar']])
class test_selection_function(CreatorCase):
model_name = 'export.selection.function'
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_value(self):
# FIXME: selection functions export the *value* itself
self.assertEqual(
self.export(1),
[[u'1']])
self.assertEqual(
self.export(3),
[[u'3']])
# fucking hell
self.assertEqual(
self.export(0),
[[False]])
class test_m2o(CreatorCase):
model_name = 'export.many2one'
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_basic(self):
""" Exported value is the name_get of the related object
"""
integer_id = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
name = dict(self.registry('export.integer').name_get(
self.cr, openerp.SUPERUSER_ID,[integer_id]))[integer_id]
self.assertEqual(
self.export(integer_id),
[[name]])
def test_path(self):
""" Can recursively export fields of m2o via path
"""
integer_id = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
self.assertEqual(
self.export(integer_id, fields=['value/.id', 'value/value']),
[[unicode(integer_id), u'42']])
def test_external_id(self):
integer_id = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
# __export__.$class.$id
external_id = u'__export__.export_many2one_%d' % integer_id
self.assertEqual(
self.export(integer_id, fields=['value/id']),
[[external_id]])
class test_o2m(CreatorCase):
model_name = 'export.one2many'
commands = [
(0, False, {'value': 4, 'str': 'record1'}),
(0, False, {'value': 42, 'str': 'record2'}),
(0, False, {'value': 36, 'str': 'record3'}),
(0, False, {'value': 4, 'str': 'record4'}),
(0, False, {'value': 13, 'str': 'record5'}),
]
names = [
u'export.one2many.child:%d' % d['value']
for c, _, d in commands
]
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_single(self):
self.assertEqual(
self.export([(0, False, {'value': 42})]),
# name_get result
[[u'export.one2many.child:42']])
def test_single_subfield(self):
self.assertEqual(
self.export([(0, False, {'value': 42})],
fields=['value', 'value/value']),
[[u'export.one2many.child:42', u'42']])
def test_integrate_one_in_parent(self):
self.assertEqual(
self.export([(0, False, {'value': 42})],
fields=['const', 'value/value']),
[[u'4', u'42']])
def test_multiple_records(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value/value']),
[
[u'4', u'4'],
[u'', u'42'],
[u'', u'36'],
[u'', u'4'],
[u'', u'13'],
])
def test_multiple_records_name(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value']),
[[
u'4', u','.join(self.names)
]])
def test_multiple_records_id(self):
export = self.export(self.commands, fields=['const', 'value/.id'])
O2M_c = self.registry('export.one2many.child')
ids = O2M_c.browse(self.cr, openerp.SUPERUSER_ID,
O2M_c.search(self.cr, openerp.SUPERUSER_ID, []))
self.assertEqual(
export,
[
['4', str(ids[0].id)],
['', str(ids[1].id)],
['', str(ids[2].id)],
['', str(ids[3].id)],
['', str(ids[4].id)],
])
def test_multiple_records_with_name_before(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value', 'value/value']),
[[ # exports sub-fields of very first o2m
u'4', u','.join(self.names), u'4'
]])
def test_multiple_records_with_name_after(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value/value', 'value']),
[ # completely ignores name_get request
[u'4', u'4', ''],
['', u'42', ''],
['', u'36', ''],
['', u'4', ''],
['', u'13', ''],
])
def test_multiple_subfields_neighbour(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value/str','value/value']),
[
[u'4', u'record1', u'4'],
['', u'record2', u'42'],
['', u'record3', u'36'],
['', u'record4', u'4'],
['', u'record5', u'13'],
])
def test_multiple_subfields_separated(self):
self.assertEqual(
self.export(self.commands, fields=['value/str', 'const', 'value/value']),
[
[u'record1', u'4', u'4'],
[u'record2', '', u'42'],
[u'record3', '', u'36'],
[u'record4', '', u'4'],
[u'record5', '', u'13'],
])
class test_o2m_multiple(CreatorCase):
model_name = 'export.one2many.multiple'
def make(self, value=None, **values):
if value is not None: values['value'] = value
id = self.model.create(self.cr, openerp.SUPERUSER_ID, values)
return self.model.browse(self.cr, openerp.SUPERUSER_ID, [id])[0]
def export(self, value=None, fields=('child1', 'child2',), context=None, **values):
record = self.make(value, **values)
return self.model._BaseModel__export_row(
self.cr, openerp.SUPERUSER_ID, record,
[f.split('/') for f in fields],
context=context)
def test_empty(self):
self.assertEqual(
self.export(child1=False, child2=False),
[[False, False]])
def test_single_per_side(self):
self.assertEqual(
self.export(child1=False, child2=[(0, False, {'value': 42})]),
[[False, u'export.one2many.child.2:42']])
self.assertEqual(
self.export(child1=[(0, False, {'value': 43})], child2=False),
[[u'export.one2many.child.1:43', False]])
self.assertEqual(
self.export(child1=[(0, False, {'value': 43})],
child2=[(0, False, {'value': 42})]),
[[u'export.one2many.child.1:43', u'export.one2many.child.2:42']])
def test_single_integrate_subfield(self):
fields = ['const', 'child1/value', 'child2/value']
self.assertEqual(
self.export(child1=False, child2=[(0, False, {'value': 42})],
fields=fields),
[[u'36', False, u'42']])
self.assertEqual(
self.export(child1=[(0, False, {'value': 43})], child2=False,
fields=fields),
[[u'36', u'43', False]])
self.assertEqual(
self.export(child1=[(0, False, {'value': 43})],
child2=[(0, False, {'value': 42})],
fields=fields),
[[u'36', u'43', u'42']])
def test_multiple(self):
""" With two "concurrent" o2ms, exports the first line combined, then
exports the rows for the first o2m, then the rows for the second o2m.
"""
fields = ['const', 'child1/value', 'child2/value']
child1 = [(0, False, {'value': v, 'str': 'record%.02d' % index})
for index, v in zip(itertools.count(), [4, 42, 36, 4, 13])]
child2 = [(0, False, {'value': v, 'str': 'record%.02d' % index})
for index, v in zip(itertools.count(10), [8, 12, 8, 55, 33, 13])]
self.assertEqual(
self.export(child1=child1, child2=False, fields=fields),
[
[u'36', u'4', False],
['', u'42', ''],
['', u'36', ''],
['', u'4', ''],
['', u'13', ''],
])
self.assertEqual(
self.export(child1=False, child2=child2, fields=fields),
[
[u'36', False, u'8'],
['', '', u'12'],
['', '', u'8'],
['', '', u'55'],
['', '', u'33'],
['', '', u'13'],
])
self.assertEqual(
self.export(child1=child1, child2=child2, fields=fields),
[
[u'36', u'4', u'8'],
['', u'42', ''],
['', u'36', ''],
['', u'4', ''],
['', u'13', ''],
['', '', u'12'],
['', '', u'8'],
['', '', u'55'],
['', '', u'33'],
['', '', u'13'],
])
class test_m2m(CreatorCase):
model_name = 'export.many2many'
commands = [
(0, False, {'value': 4, 'str': 'record000'}),
(0, False, {'value': 42, 'str': 'record001'}),
(0, False, {'value': 36, 'str': 'record010'}),
(0, False, {'value': 4, 'str': 'record011'}),
(0, False, {'value': 13, 'str': 'record100'}),
]
names = [
u'export.many2many.other:%d' % d['value']
for c, _, d in commands
]
def test_empty(self):
self.assertEqual(
self.export(False),
[[False]])
def test_single(self):
self.assertEqual(
self.export([(0, False, {'value': 42})]),
# name_get result
[[u'export.many2many.other:42']])
def test_single_subfield(self):
self.assertEqual(
self.export([(0, False, {'value': 42})],
fields=['value', 'value/value']),
[[u'export.many2many.other:42', u'42']])
def test_integrate_one_in_parent(self):
self.assertEqual(
self.export([(0, False, {'value': 42})],
fields=['const', 'value/value']),
[[u'4', u'42']])
def test_multiple_records(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value/value']),
[
[u'4', u'4'],
[u'', u'42'],
[u'', u'36'],
[u'', u'4'],
[u'', u'13'],
])
def test_multiple_records_name(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value']),
[[ # FIXME: hardcoded comma, import uses config.csv_internal_sep
# resolution: remove configurable csv_internal_sep
u'4', u','.join(self.names)
]])
# essentially same as o2m, so boring
class test_function(CreatorCase):
model_name = 'export.function'
def test_value(self):
""" Exports value normally returned by accessing the function field
"""
self.assertEqual(
self.export(42),
[[u'3']])
@@ -0,0 +1,906 @@
# -*- coding: utf-8 -*-
import openerp.modules.registry
import openerp
from openerp.tests import common
def ok(n):
""" Successful import of ``n`` records
:param int n: number of records which should have been imported
"""
return n, 0, 0, 0
def error(row, message, record=None, **kwargs):
""" Failed import of the record ``record`` at line ``row``, with the error
message ``message``
:param str message:
:param dict record:
"""
return (
-1, dict(record or {}, **kwargs),
"Line %d : %s" % (row, message),
'')
def values(seq, field='value'):
return [item[field] for item in seq]
class ImporterCase(common.TransactionCase):
model_name = False
def __init__(self, *args, **kwargs):
super(ImporterCase, self).__init__(*args, **kwargs)
self.model = None
def setUp(self):
super(ImporterCase, self).setUp()
self.model = self.registry(self.model_name)
def import_(self, fields, rows, context=None):
return self.model.import_data(
self.cr, openerp.SUPERUSER_ID, fields, rows, context=context)
def read(self, fields=('value',), domain=(), context=None):
return self.model.read(
self.cr, openerp.SUPERUSER_ID,
self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context),
fields=fields, context=context)
def browse(self, domain=(), context=None):
return self.model.browse(
self.cr, openerp.SUPERUSER_ID,
self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context),
context=context)
def xid(self, record):
ModelData = self.registry('ir.model.data')
ids = ModelData.search(
self.cr, openerp.SUPERUSER_ID,
[('model', '=', record._table_name), ('res_id', '=', record.id)])
if ids:
d = ModelData.read(
self.cr, openerp.SUPERUSER_ID, ids, ['name', 'module'])[0]
if d['module']:
return '%s.%s' % (d['module'], d['name'])
return d['name']
name = dict(record.name_get())[record.id]
# fix dotted name_get results, otherwise xid lookups blow up
name = name.replace('.', '-')
ModelData.create(self.cr, openerp.SUPERUSER_ID, {
'name': name,
'model': record._table_name,
'res_id': record.id,
'module': '__test__'
})
return '__test__.' + name
class test_ids_stuff(ImporterCase):
model_name = 'export.integer'
def test_create_with_id(self):
self.assertRaises(
Exception, # dammit
self.import_, ['.id', 'value'], [['42', '36']])
def test_create_with_xid(self):
self.assertEqual(
self.import_(['id', 'value'], [['somexmlid', '42']]),
ok(1))
self.assertEqual(
'somexmlid',
self.xid(self.browse()[0]))
def test_update_with_id(self):
id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': 36})
self.assertEqual(
36,
self.model.browse(self.cr, openerp.SUPERUSER_ID, id).value)
self.assertEqual(
self.import_(['.id', 'value'], [[str(id), '42']]),
ok(1))
self.assertEqual(
[42], # updated value to imported
values(self.read()))
def test_update_with_xid(self):
self.import_(['id', 'value'], [['somexmlid', '36']])
self.assertEqual([36], values(self.read()))
self.import_(['id', 'value'], [['somexmlid', '1234567']])
self.assertEqual([1234567], values(self.read()))
class test_boolean_field(ImporterCase):
model_name = 'export.boolean'
def test_empty(self):
self.assertEqual(
self.import_(['value'], []),
ok(0))
def test_exported(self):
self.assertEqual(
self.import_(['value'], [
['False'],
['True'],
]),
ok(2))
records = self.read()
self.assertEqual([
False,
True,
], values(records))
def test_falses(self):
self.assertEqual(
self.import_(['value'], [
[u'0'],
[u'off'],
[u'false'],
[u'FALSE'],
[u'OFF'],
[u''],
]),
ok(6))
self.assertEqual([
False,
False,
False,
False,
False,
False,
],
values(self.read()))
def test_trues(self):
self.assertEqual(
self.import_(['value'], [
['no'],
['None'],
['nil'],
['()'],
['f'],
['#f'],
# Problem: OpenOffice (and probably excel) output localized booleans
['VRAI'],
]),
ok(7))
self.assertEqual(
[True] * 7,
values(self.read()))
class test_integer_field(ImporterCase):
model_name = 'export.integer'
def test_none(self):
self.assertEqual(
self.import_(['value'], []),
ok(0))
def test_empty(self):
self.assertEqual(
self.import_(['value'], [['']]),
ok(1))
self.assertEqual(
[False],
values(self.read()))
def test_zero(self):
self.assertEqual(
self.import_(['value'], [['0']]),
ok(1))
self.assertEqual(
self.import_(['value'], [['-0']]),
ok(1))
self.assertEqual([False, False], values(self.read()))
def test_positives(self):
self.assertEqual(
self.import_(['value'], [
['1'],
['42'],
[str(2**31-1)],
['12345678']
]),
ok(4))
self.assertEqual([
1, 42, 2**31-1, 12345678
], values(self.read()))
def test_negatives(self):
self.assertEqual(
self.import_(['value'], [
['-1'],
['-42'],
[str(-(2**31 - 1))],
[str(-(2**31))],
['-12345678']
]),
ok(5))
self.assertEqual([
-1, -42, -(2**31 - 1), -(2**31), -12345678
], values(self.read()))
def test_out_of_range(self):
self.assertEqual(
self.import_(['value'], [[str(2**31)]]),
error(1, "integer out of range\n", value=2**31))
# auto-rollbacks if error is in process_liness, but not during
# ir.model.data write. Can differentiate because former ends lines
# error lines with "!"
self.cr.rollback()
self.assertEqual(
self.import_(['value'], [[str(-2**32)]]),
error(1, "integer out of range\n", value=-2**32))
def test_nonsense(self):
# FIXME: shit error reporting, exceptions half the time, messages the other half
self.assertRaises(
ValueError,
self.import_, ['value'], [['zorglub']])
class test_float_field(ImporterCase):
model_name = 'export.float'
def test_none(self):
self.assertEqual(
self.import_(['value'], []),
ok(0))
def test_empty(self):
self.assertEqual(
self.import_(['value'], [['']]),
ok(1))
self.assertEqual(
[False],
values(self.read()))
def test_zero(self):
self.assertEqual(
self.import_(['value'], [['0']]),
ok(1))
self.assertEqual(
self.import_(['value'], [['-0']]),
ok(1))
self.assertEqual([False, False], values(self.read()))
def test_positives(self):
self.assertEqual(
self.import_(['value'], [
['1'],
['42'],
[str(2**31-1)],
['12345678'],
[str(2**33)],
['0.000001'],
]),
ok(6))
self.assertEqual([
1, 42, 2**31-1, 12345678, 2.0**33, .000001
], values(self.read()))
def test_negatives(self):
self.assertEqual(
self.import_(['value'], [
['-1'],
['-42'],
[str(-2**31 + 1)],
[str(-2**31)],
['-12345678'],
[str(-2**33)],
['-0.000001'],
]),
ok(7))
self.assertEqual([
-1, -42, -(2**31 - 1), -(2**31), -12345678, -2.0**33, -.000001
], values(self.read()))
def test_nonsense(self):
self.assertRaises(
ValueError,
self.import_, ['value'], [['foobar']])
class test_string_field(ImporterCase):
model_name = 'export.string.bounded'
def test_empty(self):
self.assertEqual(
self.import_(['value'], [['']]),
ok(1))
self.assertEqual([False], values(self.read()))
def test_imported(self):
self.assertEqual(
self.import_(['value'], [
[u'foobar'],
[u'foobarbaz'],
[u'Með suð í eyrum við spilum endalaust'],
[u"People 'get' types. They use them all the time. Telling "
u"someone he can't pound a nail with a banana doesn't much "
u"surprise him."]
]),
ok(4))
self.assertEqual([
u"foobar",
u"foobarbaz",
u"Með suð í eyrum ",
u"People 'get' typ",
], values(self.read()))
class test_unbound_string_field(ImporterCase):
model_name = 'export.string'
def test_imported(self):
self.assertEqual(
self.import_(['value'], [
[u'í dag viðrar vel til loftárása'],
# ackbar.jpg
[u"If they ask you about fun, you tell them – fun is a filthy"
u" parasite"]
]),
ok(2))
self.assertEqual([
u"í dag viðrar vel til loftárása",
u"If they ask you about fun, you tell them – fun is a filthy parasite"
], values(self.read()))
class test_text(ImporterCase):
model_name = 'export.text'
def test_empty(self):
self.assertEqual(
self.import_(['value'], [['']]),
ok(1))
self.assertEqual([False], values(self.read()))
def test_imported(self):
s = (u"Breiðskífa er notað um útgefna hljómplötu sem inniheldur "
u"stúdíóupptökur frá einum flytjanda. Breiðskífur eru oftast "
u"milli 25-80 mínútur og er lengd þeirra oft miðuð við 33⅓ "
u"snúninga 12 tommu vínylplötur (sem geta verið allt að 30 mín "
u"hvor hlið).\n\nBreiðskífur eru stundum tvöfaldar og eru þær þá"
u" gefnar út á tveimur geisladiskum eða tveimur vínylplötum.")
self.assertEqual(
self.import_(['value'], [[s]]),
ok(1))
self.assertEqual([s], values(self.read()))
class test_selection(ImporterCase):
model_name = 'export.selection'
translations_fr = [
("Qux", "toto"),
("Bar", "titi"),
("Foo", "tete"),
]
def test_imported(self):
self.assertEqual(
self.import_(['value'], [
['Qux'],
['Bar'],
['Foo'],
['2'],
]),
ok(4))
self.assertEqual([3, 2, 1, 2], values(self.read()))
def test_imported_translated(self):
self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
'name': u'Français',
'code': 'fr_FR',
'translatable': True,
'date_format': '%d.%m.%Y',
'decimal_point': ',',
'thousand_sep': ' ',
})
Translations = self.registry('ir.translation')
for source, value in self.translations_fr:
Translations.create(self.cr, openerp.SUPERUSER_ID, {
'name': 'export.selection,value',
'lang': 'fr_FR',
'type': 'selection',
'src': source,
'value': value
})
# FIXME: can't import an exported selection field label if lang != en_US
# (see test_export.test_selection.test_localized_export)
self.assertEqual(
self.import_(['value'], [
['toto'],
['tete'],
['titi'],
], context={'lang': 'fr_FR'}),
ok(3))
self.assertEqual([3, 1, 2], values(self.read()))
self.assertEqual(
self.import_(['value'], [['Foo']], context={'lang': 'fr_FR'}),
error(1, "Key/value 'Foo' not found in selection field 'value'",
value=False))
def test_invalid(self):
self.assertEqual(
self.import_(['value'], [['Baz']]),
error(1, "Key/value 'Baz' not found in selection field 'value'",
# what the fuck?
value=False))
self.cr.rollback()
self.assertEqual(
self.import_(['value'], [[42]]),
error(1, "Key/value '42' not found in selection field 'value'",
value=False))
class test_selection_function(ImporterCase):
model_name = 'export.selection.function'
translations_fr = [
("Corge", "toto"),
("Grault", "titi"),
("Whee", "tete"),
("Moog", "tutu"),
]
def test_imported(self):
""" import uses fields_get, so translates import label (may or may not
be good news) *and* serializes the selection function to reverse it:
import does not actually know that the selection field uses a function
"""
# NOTE: conflict between a value and a label => ?
self.assertEqual(
self.import_(['value'], [
['3'],
["Grault"],
]),
ok(2))
self.assertEqual(
['3', '1'],
values(self.read()))
def test_translated(self):
""" Expects output of selection function returns translated labels
"""
self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
'name': u'Français',
'code': 'fr_FR',
'translatable': True,
'date_format': '%d.%m.%Y',
'decimal_point': ',',
'thousand_sep': ' ',
})
Translations = self.registry('ir.translation')
for source, value in self.translations_fr:
Translations.create(self.cr, openerp.SUPERUSER_ID, {
'name': 'export.selection,value',
'lang': 'fr_FR',
'type': 'selection',
'src': source,
'value': value
})
self.assertEqual(
self.import_(['value'], [
['toto'],
['tete'],
], context={'lang': 'fr_FR'}),
error(1, "Key/value 'toto' not found in selection field 'value'",
value=False))
self.assertEqual(
self.import_(['value'], [['Wheee']], context={'lang': 'fr_FR'}),
ok(1))
class test_m2o(ImporterCase):
model_name = 'export.many2one'
def test_by_name(self):
# create integer objects
integer_id1 = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
integer_id2 = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 36})
# get its name
name1 = dict(self.registry('export.integer').name_get(
self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1]
name2 = dict(self.registry('export.integer').name_get(
self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2]
self.assertEqual(
self.import_(['value'], [
# import by name_get
[name1],
[name1],
[name2],
]),
ok(3))
# correct ids assigned to corresponding records
self.assertEqual([
(integer_id1, name1),
(integer_id1, name1),
(integer_id2, name2),],
values(self.read()))
def test_by_xid(self):
ExportInteger = self.registry('export.integer')
integer_id = ExportInteger.create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
xid = self.xid(ExportInteger.browse(
self.cr, openerp.SUPERUSER_ID, [integer_id])[0])
self.assertEqual(
self.import_(['value/id'], [[xid]]),
ok(1))
b = self.browse()
self.assertEqual(42, b[0].value.value)
def test_by_id(self):
integer_id = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
self.assertEqual(
self.import_(['value/.id'], [[integer_id]]),
ok(1))
b = self.browse()
self.assertEqual(42, b[0].value.value)
def test_by_names(self):
integer_id1 = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
integer_id2 = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
name1 = dict(self.registry('export.integer').name_get(
self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1]
name2 = dict(self.registry('export.integer').name_get(
self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2]
# names should be the same
self.assertEqual(name1, name2)
self.assertEqual(
self.import_(['value'], [[name2]]),
ok(1))
# FIXME: is it really normal import does not care for name_search collisions?
self.assertEqual([
(integer_id1, name1)
], values(self.read()))
def test_fail_by_implicit_id(self):
""" Can't implicitly import records by id
"""
# create integer objects
integer_id1 = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
integer_id2 = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 36})
self.assertRaises(
ValueError, # Because name_search all the things. Fallback schmallback
self.import_, ['value'], [
# import by id, without specifying it
[integer_id1],
[integer_id2],
[integer_id1],
])
def test_sub_field(self):
""" Does not implicitly create the record, does not warn that you can't
import m2o subfields (at all)...
"""
self.assertRaises(
ValueError, # No record found for 42, name_searches the bloody thing
self.import_, ['value/value'], [['42']])
def test_fail_noids(self):
self.assertRaises(
ValueError,
self.import_, ['value'], [['nameisnoexist:3']])
self.cr.rollback()
self.assertRaises(
ValueError,
self.import_, ['value/id'], [['noxidhere']]),
self.cr.rollback()
self.assertRaises(
Exception, # FIXME: Why can't you be a ValueError like everybody else?
self.import_, ['value/.id'], [[66]])
class test_m2m(ImporterCase):
model_name = 'export.many2many'
# apparently, one and only thing which works is a
# csv_internal_sep-separated list of ids, xids, or names (depending if
# m2m/.id, m2m/id or m2m[/anythingelse]
def test_ids(self):
id1 = self.registry('export.many2many.other').create(
self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
id2 = self.registry('export.many2many.other').create(
self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
id3 = self.registry('export.many2many.other').create(
self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
id4 = self.registry('export.many2many.other').create(
self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
id5 = self.registry('export.many2many.other').create(
self.cr, openerp.SUPERUSER_ID, {'value': 99, 'str': 'record4'})
self.assertEqual(
self.import_(['value/.id'], [
['%d,%d' % (id1, id2)],
['%d,%d,%d' % (id1, id3, id4)],
['%d,%d,%d' % (id1, id2, id3)],
['%d' % id5]
]),
ok(4))
ids = lambda records: [record.id for record in records]
b = self.browse()
self.assertEqual(ids(b[0].value), [id1, id2])
self.assertEqual(values(b[0].value), [3, 44])
self.assertEqual(ids(b[2].value), [id1, id2, id3])
self.assertEqual(values(b[2].value), [3, 44, 84])
def test_noids(self):
try:
self.import_(['value/.id'], [['42']])
self.fail("Should have raised an exception")
except Exception, e:
self.assertIs(type(e), Exception,
"test should be fixed on exception subclass")
def test_xids(self):
M2O_o = self.registry('export.many2many.other')
id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4])
self.assertEqual(
self.import_(['value/id'], [
['%s,%s' % (self.xid(records[0]), self.xid(records[1]))],
['%s' % self.xid(records[3])],
['%s,%s' % (self.xid(records[2]), self.xid(records[1]))],
]),
ok(3))
b = self.browse()
self.assertEqual(values(b[0].value), [3, 44])
self.assertEqual(values(b[2].value), [44, 84])
def test_noxids(self):
self.assertRaises(
ValueError,
self.import_, ['value/id'], [['noxidforthat']])
def test_names(self):
M2O_o = self.registry('export.many2many.other')
id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4])
name = lambda record: dict(record.name_get())[record.id]
self.assertEqual(
self.import_(['value'], [
['%s,%s' % (name(records[1]), name(records[2]))],
['%s,%s,%s' % (name(records[0]), name(records[1]), name(records[2]))],
['%s,%s' % (name(records[0]), name(records[3]))],
]),
ok(3))
b = self.browse()
self.assertEqual(values(b[1].value), [3, 44, 84])
self.assertEqual(values(b[2].value), [3, 9])
def test_nonames(self):
self.assertRaises(
ValueError,
self.import_, ['value'], [['wherethem2mhavenonames']])
def test_import_to_existing(self):
M2O_o = self.registry('export.many2many.other')
id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
xid = 'myxid'
self.assertEqual(
self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id1, id2)]]),
ok(1))
self.assertEqual(
self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id3, id4)]]),
ok(1))
b = self.browse()
self.assertEqual(len(b), 1)
# TODO: replacement of existing m2m values is correct?
self.assertEqual(values(b[0].value), [84, 9])
class test_o2m(ImporterCase):
model_name = 'export.one2many'
def test_single(self):
self.assertEqual(
self.import_(['const', 'value/value'], [
['5', '63']
]),
ok(1))
(b,) = self.browse()
self.assertEqual(b.const, 5)
self.assertEqual(values(b.value), [63])
def test_multicore(self):
self.assertEqual(
self.import_(['const', 'value/value'], [
['5', '63'],
['6', '64'],
]),
ok(2))
b1, b2 = self.browse()
self.assertEqual(b1.const, 5)
self.assertEqual(values(b1.value), [63])
self.assertEqual(b2.const, 6)
self.assertEqual(values(b2.value), [64])
def test_multisub(self):
self.assertEqual(
self.import_(['const', 'value/value'], [
['5', '63'],
['', '64'],
['', '65'],
['', '66'],
]),
ok(4))
(b,) = self.browse()
self.assertEqual(values(b.value), [63, 64, 65, 66])
def test_multi_subfields(self):
self.assertEqual(
self.import_(['value/str', 'const', 'value/value'], [
['this', '5', '63'],
['is', '', '64'],
['the', '', '65'],
['rhythm', '', '66'],
]),
ok(4))
(b,) = self.browse()
self.assertEqual(values(b.value), [63, 64, 65, 66])
self.assertEqual(
values(b.value, 'str'),
'this is the rhythm'.split())
def test_link_inline(self):
id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
'str': 'Bf', 'value': 109
})
id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
'str': 'Me', 'value': 262
})
try:
self.import_(['const', 'value/.id'], [
['42', '%d,%d' % (id1, id2)]
])
except ValueError, e:
# should be Exception(Database ID doesn't exist: export.one2many.child : $id1,$id2)
self.assertIs(type(e), ValueError)
self.assertEqual(
e.args[0],
"invalid literal for int() with base 10: '%d,%d'" % (id1, id2))
def test_link(self):
id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
'str': 'Bf', 'value': 109
})
id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
'str': 'Me', 'value': 262
})
self.assertEqual(
self.import_(['const', 'value/.id'], [
['42', str(id1)],
['', str(id2)],
]),
ok(2))
# No record values alongside id => o2m resolution skipped altogether,
# creates 2 records => remove/don't import columns sideshow columns,
# get completely different semantics
b, b1 = self.browse()
self.assertEqual(b.const, 42)
self.assertEqual(values(b.value), [])
self.assertEqual(b1.const, 4)
self.assertEqual(values(b1.value), [])
def test_link_2(self):
O2M_c = self.registry('export.one2many.child')
id1 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
'str': 'Bf', 'value': 109
})
id2 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
'str': 'Me', 'value': 262
})
self.assertEqual(
self.import_(['const', 'value/.id', 'value/value'], [
['42', str(id1), '1'],
['', str(id2), '2'],
]),
ok(2))
(b,) = self.browse()
# if an id (db or xid) is provided, expectations that objects are
# *already* linked and emits UPDATE (1, id, {}).
# Noid => CREATE (0, ?, {})
# TODO: xid ignored aside from getting corresponding db id?
self.assertEqual(b.const, 42)
self.assertEqual(values(b.value), [])
# FIXME: updates somebody else's records?
self.assertEqual(
O2M_c.read(self.cr, openerp.SUPERUSER_ID, id1),
{'id': id1, 'str': 'Bf', 'value': 1, 'parent_id': False})
self.assertEqual(
O2M_c.read(self.cr, openerp.SUPERUSER_ID, id2),
{'id': id2, 'str': 'Me', 'value': 2, 'parent_id': False})
class test_o2m_multiple(ImporterCase):
model_name = 'export.one2many.multiple'
def test_multi_mixed(self):
self.assertEqual(
self.import_(['const', 'child1/value', 'child2/value'], [
['5', '11', '21'],
['', '12', '22'],
['', '13', '23'],
['', '14', ''],
]),
ok(4))
# Oh yeah, that's the stuff
(b, b1, b2) = self.browse()
self.assertEqual(values(b.child1), [11])
self.assertEqual(values(b.child2), [21])
self.assertEqual(values(b1.child1), [12])
self.assertEqual(values(b1.child2), [22])
self.assertEqual(values(b2.child1), [13, 14])
self.assertEqual(values(b2.child2), [23])
def test_multi(self):
self.assertEqual(
self.import_(['const', 'child1/value', 'child2/value'], [
['5', '11', '21'],
['', '12', ''],
['', '13', ''],
['', '14', ''],
['', '', '22'],
['', '', '23'],
]),
ok(6))
# What the actual fuck?
(b, b1) = self.browse()
self.assertEqual(values(b.child1), [11, 12, 13, 14])
self.assertEqual(values(b.child2), [21])
self.assertEqual(values(b1.child2), [22, 23])
def test_multi_fullsplit(self):
self.assertEqual(
self.import_(['const', 'child1/value', 'child2/value'], [
['5', '11', ''],
['', '12', ''],
['', '13', ''],
['', '14', ''],
['', '', '21'],
['', '', '22'],
['', '', '23'],
]),
ok(7))
# oh wow
(b, b1) = self.browse()
self.assertEqual(b.const, 5)
self.assertEqual(values(b.child1), [11, 12, 13, 14])
self.assertEqual(b1.const, 36)
self.assertEqual(values(b1.child2), [21, 22, 23])
# function, related, reference: written to db as-is...
# => function uses @type for value coercion/conversion
+4 -1
View File
@@ -39,13 +39,16 @@ class TransactionCase(unittest2.TestCase):
"""
def setUp(self):
self.cr = openerp.modules.registry.RegistryManager.get(DB).db.cursor()
self.cr = self.cursor()
self.uid = openerp.SUPERUSER_ID
def tearDown(self):
self.cr.rollback()
self.cr.close()
def cursor(self):
return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
def registry(self, model):
return openerp.modules.registry.RegistryManager.get(DB)[model]
+1
View File
@@ -26,6 +26,7 @@ from misc import *
from convert import *
from translate import *
from graph import graph
from image import *
from amount_to_text import *
from amount_to_text_en import *
from pdf_utils import *
+139
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
+5 -1
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.