diff --git a/debian/control b/debian/control index ce0d3df55e1..f5d425c5586 100644 --- a/debian/control +++ b/debian/control @@ -16,6 +16,7 @@ Depends: python, postgresql-client, python-dateutil, + python-docutils, python-feedparser, python-gdata, python-ldap, diff --git a/openerp-server b/openerp-server index 1348d5a6315..3c6fa117da2 100755 --- a/openerp-server +++ b/openerp-server @@ -30,7 +30,6 @@ GNU Public Licence. (c) 2003-TODAY, Fabien Pinckaers - OpenERP SA """ -import imp import logging import os import signal diff --git a/openerp/addons/base/i18n/es_MX.po b/openerp/addons/base/i18n/es_MX.po index dbc674d754a..cc9d519e705 100644 --- a/openerp/addons/base/i18n/es_MX.po +++ b/openerp/addons/base/i18n/es_MX.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-09-07 04:55+0000\n" +"X-Launchpad-Export-Date: 2012-09-08 04:54+0000\n" "X-Generator: Launchpad (build 15914)\n" #. module: base diff --git a/openerp/addons/base/i18n/ru.po b/openerp/addons/base/i18n/ru.po index 365f8db415a..92d742a292b 100644 --- a/openerp/addons/base/i18n/ru.po +++ b/openerp/addons/base/i18n/ru.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:44+0000\n" -"PO-Revision-Date: 2012-08-20 15:45+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2012-09-10 14:37+0000\n" +"Last-Translator: Chertykov Denis \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-09-05 04:43+0000\n" -"X-Generator: Launchpad (build 15901)\n" +"X-Launchpad-Export-Date: 2012-09-11 04:50+0000\n" +"X-Generator: Launchpad (build 15924)\n" #. module: base #: model:res.country,name:base.sh @@ -604,7 +604,7 @@ msgstr "Испанский (VE) / Español (VE)" #. module: base #: model:ir.module.module,shortdesc:base.module_hr_timesheet_invoice msgid "Invoice on Timesheets" -msgstr "" +msgstr "Счет по затратам времени" #. module: base #: view:base.module.upgrade:0 @@ -1269,7 +1269,7 @@ msgstr "Порт SMTP" #. module: base #: model:ir.module.module,shortdesc:base.module_import_sugarcrm msgid "SugarCRM Import" -msgstr "" +msgstr "Импорт SugarCRM" #. module: base #: view:res.lang:0 @@ -1374,7 +1374,7 @@ msgstr "Система управления документами" #. module: base #: model:ir.module.module,shortdesc:base.module_crm_claim msgid "Claims Management" -msgstr "" +msgstr "Управление претензиями" #. module: base #: model:ir.ui.menu,name:base.menu_purchase_root @@ -1749,6 +1749,12 @@ msgid "" " Apply Different Category for the product.\n" " " msgstr "" +"\n" +" Базовый модуль управления ланчами\n" +"\n" +" Отслеживание заказов, движения средств, кассы, продукции.\n" +" Возможность разнесения продукции по категориям.\n" +" " #. module: base #: model:res.country,name:base.kg @@ -1862,7 +1868,7 @@ msgstr "" #. module: base #: model:ir.module.module,shortdesc:base.module_html_view msgid "Html View" -msgstr "" +msgstr "Просмотр HTML" #. module: base #: field:res.currency,position:0 @@ -2533,7 +2539,7 @@ msgstr "Идеи" #. module: base #: model:ir.module.module,shortdesc:base.module_sale_crm msgid "Opportunity to Quotation" -msgstr "" +msgstr "Предложение в Запрос цен" #. module: base #: model:ir.module.module,description:base.module_sale_analytic_plans @@ -2767,7 +2773,7 @@ msgstr "Словения" #. module: base #: help:res.currency,name:0 msgid "Currency Code (ISO 4217)" -msgstr "" +msgstr "Код валюты (ISO 4217)" #. module: base #: model:ir.actions.act_window,name:base.res_log_act_window @@ -3267,7 +3273,7 @@ msgstr "Режим SMTP-over-SSL недоступен" #. module: base #: model:ir.module.module,shortdesc:base.module_survey msgid "Survey" -msgstr "" +msgstr "Опрос" #. module: base #: view:base.language.import:0 @@ -3315,7 +3321,7 @@ msgstr "Уругвай" #. module: base #: model:ir.module.module,shortdesc:base.module_fetchmail_crm msgid "eMail Gateway for Leads" -msgstr "" +msgstr "Шлюз эл.почты для кандидатов" #. module: base #: selection:base.language.install,lang:0 @@ -3530,7 +3536,7 @@ msgstr "" #. module: base #: model:ir.module.module,shortdesc:base.module_report_webkit msgid "Webkit Report Engine" -msgstr "" +msgstr "Инструмент создания отчетов Webkit" #. module: base #: selection:publisher_warranty.contract,state:0 @@ -3557,7 +3563,7 @@ msgstr "Майотта" #. module: base #: model:ir.module.module,shortdesc:base.module_crm_todo msgid "Tasks on CRM" -msgstr "" +msgstr "Задачи по CRM" #. module: base #: model:ir.module.category,name:base.module_category_generic_modules_accounting @@ -3683,12 +3689,12 @@ msgstr "Обнаружена рекурсия." #. module: base #: model:ir.module.module,shortdesc:base.module_report_webkit_sample msgid "Webkit Report Samples" -msgstr "" +msgstr "Примеры отчетов Webkit" #. module: base #: model:ir.module.module,shortdesc:base.module_point_of_sale msgid "Point Of Sale" -msgstr "" +msgstr "Точка продажи" #. module: base #: code:addons/base/module/module.py:302 @@ -3812,7 +3818,7 @@ msgstr "Проверка кода Ean" #. module: base #: field:res.partner,vat:0 msgid "VAT" -msgstr "ИНН" +msgstr "НДС" #. module: base #: field:res.users,new_password:0 @@ -3945,7 +3951,7 @@ msgstr "Поддержка iCal" #. module: base #: model:ir.module.module,shortdesc:base.module_fetchmail msgid "Email Gateway" -msgstr "" +msgstr "Шлюз эл.почты" #. module: base #: code:addons/base/ir/ir_mail_server.py:439 @@ -4069,7 +4075,7 @@ msgstr "Португалия" #. module: base #: model:ir.module.module,shortdesc:base.module_share msgid "Share any Document" -msgstr "" +msgstr "Совместный доступ r любым документам" #. module: base #: field:ir.module.module,certificate:0 @@ -4407,7 +4413,7 @@ msgstr "Код должен быть уникальным" #. module: base #: model:ir.module.module,shortdesc:base.module_hr_expense msgid "Expenses Management" -msgstr "" +msgstr "Управление расходами" #. module: base #: view:workflow.activity:0 @@ -4418,7 +4424,7 @@ msgstr "Входящие переходы" #. module: base #: field:ir.values,value_unpickle:0 msgid "Default value or action reference" -msgstr "" +msgstr "Значение по умолчанию или ссылка на действие" #. module: base #: model:res.country,name:base.sr @@ -4558,7 +4564,7 @@ msgstr "Экваториальная Гвинея" #. module: base #: model:ir.module.module,shortdesc:base.module_warning msgid "Warning Messages and Alerts" -msgstr "" +msgstr "Предупреждающие сообщения и оповещения" #. module: base #: view:base.module.import:0 @@ -4870,7 +4876,7 @@ msgstr "Договор уже зарегистрирован в системе." #: model:ir.actions.act_window,name:base.action_res_partner_bank_type_form #: model:ir.ui.menu,name:base.menu_action_res_partner_bank_typeform msgid "Bank Account Types" -msgstr "" +msgstr "Типы банковского счета" #. module: base #: help:ir.sequence,suffix:0 @@ -4880,7 +4886,7 @@ msgstr "Суффикс записи для нумерации" #. module: base #: help:ir.mail_server,smtp_user:0 msgid "Optional username for SMTP authentication" -msgstr "" +msgstr "Необязательное имя пользователя для аутентификации SMTP" #. module: base #: model:ir.model,name:base.model_ir_actions_actions @@ -4935,7 +4941,7 @@ msgstr "" #. module: base #: model:ir.module.module,shortdesc:base.module_web_chat msgid "Web Chat" -msgstr "" +msgstr "Веб-чат" #. module: base #: field:res.company,rml_footer2:0 @@ -4966,7 +4972,7 @@ msgstr "Права доступа" #: code:addons/base/ir/ir_model.py:311 #, python-format msgid "Changing the storing system for field \"%s\" is not allowed." -msgstr "" +msgstr "Изменение системы хранения для поля \"%s\" не допускается." #. module: base #: help:res.partner.bank,company_id:0 diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 77f305b28be..fc13d3affc3 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -28,6 +28,7 @@ import re import shutil import tempfile import urllib +import urllib2 import zipfile import zipimport @@ -36,8 +37,6 @@ try: except ImportError: from StringIO import StringIO # NOQA -import requests - import openerp from openerp import modules, pooler, release, tools, addons from openerp.tools.parse_version import parse_version @@ -630,13 +629,12 @@ class module(osv.osv): try: for module_name in urls: try: - r = requests.get(urls[module_name]) - r.raise_for_status() - except requests.HTTPError, e: + content = urllib2.urlopen(urls[module_name]).read() + except Exception, e: _logger.exception('ggr') raise osv.except_osv('grrr', e) else: - zipfile.ZipFile(StringIO(r.content)).extractall(tmp) + zipfile.ZipFile(StringIO(content)).extractall(tmp) assert os.path.isdir(os.path.join(tmp, module_name)) for module_name in urls: diff --git a/openerp/addons/base/report/corporate_defaults.xml b/openerp/addons/base/report/corporate_defaults.xml index 12e8bb5eeea..751693e7e89 100644 --- a/openerp/addons/base/report/corporate_defaults.xml +++ b/openerp/addons/base/report/corporate_defaults.xml @@ -2,8 +2,7 @@ - - + <name type="field" name="partner_id.name"/> <street type="field" name="street"/> diff --git a/openerp/addons/base/report/corporate_defaults.xsl b/openerp/addons/base/report/corporate_defaults.xsl index e0f6918ec59..9e976381e48 100644 --- a/openerp/addons/base/report/corporate_defaults.xsl +++ b/openerp/addons/base/report/corporate_defaults.xsl @@ -39,8 +39,7 @@ <!--page bottom--> <lines>1.5cm 2.2cm 19.9cm 2.2cm</lines> - <drawCentredString x="10.5cm" y="1.7cm"><xsl:value-of select="//corporate-header/corporation/rml_footer1"/></drawCentredString> - <drawCentredString x="10.5cm" y="1.25cm"><xsl:value-of select="//corporate-header/corporation/rml_footer2"/></drawCentredString> + <drawCentredString x="10.5cm" y="1.7cm"><xsl:value-of select="//corporate-header/corporation/rml_footer"/></drawCentredString> <drawCentredString x="10.5cm" y="0.8cm">Your contact : <xsl:value-of select="//corporate-header/user/name"/></drawCentredString> </xsl:template> @@ -73,8 +72,7 @@ <!--page bottom--> <lines>1.5cm 1.2cm 19.9cm 1.2cm</lines> - <drawCentredString x="10.5cm" y="1.7cm"><xsl:value-of select="//corporate-header/corporation/rml_footer1"/></drawCentredString> - <drawCentredString x="10.5cm" y="1.25cm"><xsl:value-of select="//corporate-header/corporation/rml_footer2"/></drawCentredString> + <drawCentredString x="10.5cm" y="1.7cm"><xsl:value-of select="//corporate-header/corporation/rml_footer"/></drawCentredString> <!-- <drawCentredString x="10.5cm" y="0.8cm">Your contact : <xsl:value-of select="//corporate-header/user/name"/></drawCentredString>--> </xsl:template> diff --git a/openerp/addons/base/report/corporate_sxw_header.xml b/openerp/addons/base/report/corporate_sxw_header.xml index d752ffa91a5..6cd8e5a218f 100644 --- a/openerp/addons/base/report/corporate_sxw_header.xml +++ b/openerp/addons/base/report/corporate_sxw_header.xml @@ -233,8 +233,7 @@ <table:table-column table:style-name="Table1.A"/> <table:table-row> <table:table-cell table:style-name="Table1.A1" table:value-type="string"> - <text:p text:style-name="P7">[[ company.rml_footer1 ]]</text:p> - <text:p text:style-name="P7">[[ company.rml_footer2 ]]</text:p> + <text:p text:style-name="P7">[[ company.rml_footer ]]</text:p> <text:p text:style-name="P7">Contact : [[ user.name ]]</text:p> </table:table-cell> </table:table-row> diff --git a/openerp/addons/base/report/mako_footer.html b/openerp/addons/base/report/mako_footer.html index 7c2d5b67a08..9f8990998a7 100644 --- a/openerp/addons/base/report/mako_footer.html +++ b/openerp/addons/base/report/mako_footer.html @@ -7,15 +7,8 @@ </tr> <tr> <td> - % if company['rml_footer1']: - <p align = "center"><small><b>${company.rml_footer1}</b></small></br></p> - %endif - </td> - </tr> - <tr> - <td> - % if company['rml_footer2']: - <p align = "center"><small><b>${company.rml_footer2}</b></small></br></p> + % if company['rml_footer']: + <p align="center"><small><b>${company.rml_footer}</b></small></p> %endif </td> </tr> diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index c5c05c1807c..a839afc10e4 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -109,7 +109,7 @@ class res_partner_bank(osv.osv): if not context.get('address'): return value - for address in self.pool.get('res.partner').resolve_o2m_commands_to_record_dicts( + for address in self.pool.get('res.partner').resolve_2many_commands( cursor, user, 'address', context['address'], ['type', field], context=context): if address.get('type') == 'default': diff --git a/openerp/addons/base/res/res_bank_view.xml b/openerp/addons/base/res/res_bank_view.xml index 0c88ceeff7c..8a8fa0e2922 100644 --- a/openerp/addons/base/res/res_bank_view.xml +++ b/openerp/addons/base/res/res_bank_view.xml @@ -91,8 +91,9 @@ <group col="4"> <field name="state"/> <field name="acc_number"/> - <field name="company_id" on_change="onchange_company_id(company_id)" invisible="context.get('company_hide', True)" widget="selection"/> - <field name="footer" attrs="{'invisible': [('company_id','=',False)]}"/> + <field name="company_id" on_change="onchange_company_id(company_id)" + invisible="context.get('company_hide', True)" widget="selection"/> + <field name="footer" invisible="context.get('footer_hide', True)"/> </group> <group> <group name="owner" string="Bank Account Owner"> @@ -128,7 +129,7 @@ <field name="acc_number"/> <field name="bank_name"/> <field name="company_id" invisible="context.get('company_hide', True)"/> - <field name="footer" invisible="context.get('company_hide', True)"/> + <field name="footer" invisible="context.get('footer_hide', True)"/> <field name="partner_id"/> </tree> </field> diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py index c4c9ba585db..83b24b55a93 100644 --- a/openerp/addons/base/res/res_company.py +++ b/openerp/addons/base/res/res_company.py @@ -89,7 +89,6 @@ class res_company(osv.osv): result[company.id][field] = address[field] or False return result - def _set_address_data(self, cr, uid, company_id, name, value, arg, context=None): """ Write the 'address' functional fields. """ company = self.browse(cr, uid, company_id, context=context) @@ -103,18 +102,18 @@ class res_company(osv.osv): part_obj.create(cr, uid, {name: value or False, 'parent_id': company.partner_id.id}, context=context) return True - _columns = { 'name': fields.related('partner_id', 'name', string='Company Name', size=128, required=True, store=True, type='char'), 'parent_id': fields.many2one('res.company', 'Parent Company', select=True), 'child_ids': fields.one2many('res.company', 'parent_id', 'Child Companies'), 'partner_id': fields.many2one('res.partner', 'Partner', required=True), - 'rml_header1': fields.char('Company Slogan', size=200, help="Appears by default on the top right corner of your printed documents (report header)."), - 'rml_footer1': fields.char('General Information Footer', size=200), - 'rml_footer2': fields.char('Bank Accounts Footer', size=250, help="Write here your bank accounts for customer payments."), 'rml_header': fields.text('RML Header', required=True), + 'rml_header1': fields.char('Company Slogan', size=200, help="Appears by default on the top right corner of your printed documents (report header)."), 'rml_header2': fields.text('RML Internal Header', required=True), 'rml_header3': fields.text('RML Internal Header for Landscape Reports', required=True), + 'rml_footer': fields.text('Report Footer', help="Footer text displayed at the bottom of all reports. Automatically set based on company details, "\ + "but may also be customized by directly editing it."), + 'custom_footer': fields.boolean('Custom Footer', help="Check this to define the report footer manually. Otherwise it will be filled in automatically."), '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'), @@ -138,15 +137,38 @@ class res_company(osv.osv): _sql_constraints = [ ('name_uniq', 'unique (name)', 'The company name must be unique !') ] - def on_change_header(self, cr, uid, ids, phone, email, fax, website, vat, reg=False, context=None): - val = [] - if phone: val.append(_('Phone: ')+phone) - if fax: val.append(_('Fax: ')+fax) - if website: val.append(_('Website: ')+website) - if vat: val.append(_('TIN: ')+vat) - if reg: val.append(_('Reg: ')+reg) - return {'value': {'rml_footer1':' | '.join(val)}} - + + def onchange_footer(self, cr, uid, ids, context=None): + # when touched, the footer becomes custom + return {'value': {'custom_footer': True}} + + def set_auto_footer(self, cr, uid, ids, context=None): + # unset the flag 'custom_footer'; this will automatically compute the footer + return self.write(cr, uid, ids, {'custom_footer': False}, context=context) + + def compute_footer(self, cr, uid, ids, context=None): + res_partner_bank = self.pool.get('res.partner.bank') + for company in self.browse(cr, uid, ids, context): + if not company.custom_footer: + # first line (notice that missing elements are filtered out before the join) + res = ' | '.join(filter(bool, [ + company.phone and '%s: %s' % (_('Phone'), company.phone), + company.fax and '%s: %s' % (_('Fax'), company.fax), + company.email and '%s: %s' % (_('Email'), company.email), + company.website and '%s: %s' % (_('Website'), company.website), + company.vat and '%s: %s' % (_('TIN'), company.vat), + company.company_registry and '%s: %s' % (_('Reg'), company.company_registry), + ])) + # second line: bank accounts + account_ids = [acc.id for acc in company.bank_ids if acc.footer] + account_names = res_partner_bank.name_get(cr, uid, account_ids, context=context) + if account_names: + title = _('Bank Accounts') if len(account_names) > 1 else _('Bank Account') + res += '\n%s: %s' % (title, ', '.join(name for id, name in account_names)) + # update footer + self.write(cr, uid, [company.id], {'rml_footer': res}, context=context) + return True + def on_change_country(self, cr, uid, ids, country_id, context=None): currency_id = self._get_euro(cr, uid, context=context) if country_id: @@ -226,11 +248,17 @@ class res_company(osv.osv): self.cache_restart(cr) company_id = super(res_company, self).create(cr, uid, vals, context=context) obj_partner.write(cr, uid, partner_id, {'company_id': company_id}, context=context) + self.compute_footer(cr, uid, [company_id], context=context) return company_id - def write(self, cr, *args, **argv): + def write(self, cr, uid, ids, values, context=None): self.cache_restart(cr) - return super(res_company, self).write(cr, *args, **argv) + if isinstance(ids, (int, long)): + ids = [ids] + super(res_company, self).write(cr, uid, ids, values, context=context) + if 'rml_footer' not in values: + self.compute_footer(cr, uid, ids, context=context) + return True def _get_euro(self, cr, uid, context=None): rate_obj = self.pool.get('res.currency.rate') @@ -272,20 +300,22 @@ class res_company(osv.osv): return self._header_a4 _header_main = """ - <header> +<header> <pageTemplate> - <frame id="first" x1="1.3cm" y1="2.5cm" height="%s" width="19.0cm"/> + <frame id="first" x1="1.3cm" y1="3.0cm" height="%s" width="19.0cm"/> + <stylesheet> + <paraStyle name="main_footer" fontName="DejaVu Sans" fontSize="8.0" alignment="CENTER"/> + </stylesheet> <pageGraphics> <!-- You Logo - Change X,Y,Width and Height --> <image x="1.3cm" y="%s" height="40.0" >[[ company.logo or removeParentNode('image') ]]</image> <setFont name="DejaVu Sans" size="8"/> <fill color="black"/> <stroke color="black"/> + + <!-- page header --> <lines>1.3cm %s 20cm %s</lines> - <drawRightString x="20cm" y="%s">[[ company.rml_header1 ]]</drawRightString> - - <drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString> <drawString x="1.3cm" y="%s">[[ company.partner_id.street or '' ]]</drawString> <drawString x="1.3cm" y="%s">[[ company.partner_id.city or '' ]] - [[ company.partner_id.country_id and company.partner_id.country_id.name or '']]</drawString> @@ -295,13 +325,19 @@ class res_company(osv.osv): <drawRightString x="7cm" y="%s">[[ company.partner_id.email or '' ]]</drawRightString> <lines>1.3cm %s 7cm %s</lines> + <!-- left margin --> + <rotate degrees="90"/> + <fill color="grey"/> + <drawString x="2.65cm" y="-0.4cm">produced by OpenERP.com</drawString> + <fill color="black"/> + <rotate degrees="-90"/> + <!--page bottom--> - - <lines>1.2cm 2.15cm 19.9cm 2.15cm</lines> - - <drawCentredString x="10.5cm" y="1.7cm">[[ company.rml_footer1 ]]</drawCentredString> - <drawCentredString x="10.5cm" y="1.25cm">[[ company.rml_footer2 ]]</drawCentredString> - <drawCentredString x="10.5cm" y="0.8cm">Contact : [[ user.name ]] - Page: <pageNumber/></drawCentredString> + <lines>1.2cm 2.65cm 19.9cm 2.65cm</lines> + <place x="1.3cm" y="0cm" height="2.55cm" width="19.0cm"> + <para style="main_footer">[[ company.rml_footer ]]</para> + <para style="main_footer">Contact : [[ user.name ]] - Page: <pageNumber/></para> + </place> </pageGraphics> </pageTemplate> </header>""" diff --git a/openerp/addons/base/res/res_company_view.xml b/openerp/addons/base/res/res_company_view.xml index b0db4748974..b92cfba19b6 100644 --- a/openerp/addons/base/res/res_company_view.xml +++ b/openerp/addons/base/res/res_company_view.xml @@ -31,19 +31,15 @@ <h1> <field name="name" class="oe_inline"/> </h1> - <label for="rml_header1" class="oe_edit_only"/> - <div> - <field name="rml_header1" placeholder="e.g. Global Business Solutions"/> - </div> </div> <group col="4"> - <field name="partner_id" readonly="1" required="0" groups="base.group_no_one"/> <field name="parent_id" groups="base.group_multi_company"/> </group> <notebook colspan="4"> <page string="General Information"> <group> <group> + <field name="partner_id" readonly="1" required="0" groups="base.group_no_one"/> <label for="street" string="Address"/> <div> <field name="street" placeholder="Street..."/> @@ -55,30 +51,36 @@ </div> <field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": true}' on_change="on_change_country(country_id)"/> </div> + <label for="rml_header1"/> + <div> + <field name="rml_header1" placeholder="e.g. Global Business Solutions"/> + </div> <field name="website" widget="url" placeholder="e.g. www.openerp.com"/> </group> <group> - <field name="phone" on_change="on_change_header(phone, email, fax, website, vat, company_registry)"/> - <field name="fax" on_change="on_change_header(phone, email, fax, website, vat, company_registry)"/> - <field name="email" on_change="on_change_header(phone, email, fax, website, vat, company_registry)"/> - <field name="vat" on_change="on_change_header(phone, email, fax, website, vat, company_registry)"/> - <field name="company_registry" on_change="on_change_header(phone, email, fax, website, vat, company_registry)"/> + <field name="phone"/> + <field name="fax"/> + <field name="email"/> + <field name="vat"/> + <field name="company_registry"/> </group> </group> <group string="Bank Accounts"> - <field name="bank_ids" colspan="4" nolabel="1"/> - <label for="rml_footer2"/> - <div name="bank_acc"> - <field name="rml_footer2"/> - <!--<button name="%(bank_account_update)d" string="Set Bank Accounts" type="action" icon="gtk-go-forward" class="oe_inline"/>--> + <field name="bank_ids" nolabel="1" + context="{'default_company_id': active_id, 'footer_hide': False}"/> + </group> + <group string="Report Footer Configuration"> + <field name="paper_format" on_change="onchange_paper_format(paper_format)"/> + <field name="custom_footer" invisible="1"/> + <label for="rml_footer"/> + <div> + <field name="rml_footer" on_change="onchange_footer()"/> + <button string="Set Automatic Footer" type="object" name="set_auto_footer" + attrs="{'invisible': [('custom_footer','=',False)]}" class="oe_edit_only"/> </div> </group> </page> <page string="Header/Footer" groups="base.group_no_one"> - <group> - <field name="paper_format" on_change="onchange_paper_format(paper_format)"/> - <field name="rml_footer1"/> - </group> <label for="rml_header"/> <field name="rml_header"/> <label for="rml_header2"/> @@ -162,7 +164,7 @@ <field name="sequence"/> </group> </group> - </sheet> + </sheet> </form> </field> </record> @@ -190,4 +192,3 @@ </data> </openerp> - diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 1609fa2a16f..906f4b2b6c6 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -21,7 +21,6 @@ import math import openerp -import os from osv import osv, fields import re import tools @@ -58,20 +57,20 @@ class res_partner_category(osv.osv): return super(res_partner_category, self).name_get(cr, uid, ids, context=context) if isinstance(ids, (int, long)): ids = [ids] - reads = self.read(cr, uid, ids, ['name','parent_id'], context=context) + reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context) res = [] for record in reads: name = record['name'] if record['parent_id']: - name = record['parent_id'][1]+' / '+name + name = record['parent_id'][1] + ' / ' + name res.append((record['id'], name)) return res def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): if not args: - args=[] + args = [] if not context: - context={} + context = {} if name: # Be sure name_search is symetric to name_get name = name.split(' / ')[-1] @@ -85,23 +84,23 @@ class res_partner_category(osv.osv): res = self.name_get(cr, uid, ids, context=context) return dict(res) - _description='Partner Categories' + _description = 'Partner Categories' _name = 'res.partner.category' _columns = { 'name': fields.char('Category Name', required=True, size=64, translate=True), 'parent_id': fields.many2one('res.partner.category', 'Parent Category', select=True, ondelete='cascade'), 'complete_name': fields.function(_name_get_fnc, type="char", string='Full Name'), 'child_ids': fields.one2many('res.partner.category', 'parent_id', 'Child Categories'), - 'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."), - 'parent_left' : fields.integer('Left parent', select=True), - 'parent_right' : fields.integer('Right parent', select=True), + 'active': fields.boolean('Active', help="The active field allows you to hide the category without removing it."), + 'parent_left': fields.integer('Left parent', select=True), + 'parent_right': fields.integer('Right parent', select=True), 'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'), } _constraints = [ (osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id']) ] _defaults = { - 'active' : lambda *a: 1, + 'active': lambda *a: 1, } _parent_store = True _parent_order = 'name' @@ -113,7 +112,7 @@ class res_partner_title(osv.osv): _columns = { 'name': fields.char('Title', required=True, size=46, translate=True), 'shortcut': fields.char('Abbreviation', size=16, translate=True), - 'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24) + 'domain': fields.selection([('partner', 'Partner'), ('contact', 'Contact')], 'Domain', required=True, size=24) } _defaults = { 'domain': 'contact', @@ -129,13 +128,13 @@ POSTAL_ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'countr ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang') class res_partner(osv.osv): - _description='Partner' + _description = 'Partner' _name = "res.partner" def _address_display(self, cr, uid, ids, name, args, context=None): - res={} + res = {} for partner in self.browse(cr, uid, ids, context=context): - res[partner.id] =self._display_address(cr, uid, partner, context=context) + res[partner.id] = self._display_address(cr, uid, partner, context=context) return res def _get_image(self, cr, uid, ids, name, args, context=None): @@ -143,7 +142,7 @@ class res_partner(osv.osv): 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) @@ -151,7 +150,7 @@ class res_partner(osv.osv): _columns = { 'name': fields.char('Name', size=128, required=True, select=True), 'date': fields.date('Date', select=1), - 'title': fields.many2one('res.partner.title','Title'), + 'title': fields.many2one('res.partner.title', 'Title'), 'parent_id': fields.many2one('res.partner', 'Owned by'), 'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'), 'ref': fields.char('Reference', size=64, select=1), @@ -162,9 +161,9 @@ class res_partner(osv.osv): "It is important to set a value for this field. You should use the same timezone " "that is otherwise used to pick and render date and time values: your computer's timezone."), 'user_id': fields.many2one('res.users', 'Salesperson', help='The internal user that is in charge of communicating with this partner if any.'), - 'vat': fields.char('TIN',size=32 ,help="Tax Identification Number. Check the box if the partner is subjected to taxes. Used by the some of the legal statements."), + 'vat': fields.char('TIN', size=32, help="Tax Identification Number. Check the box if the partner is subjected to taxes. Used by the some of the legal statements."), 'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'), - 'website': fields.char('Website',size=64, help="Website of Partner or Company"), + 'website': fields.char('Website', size=64, help="Website of Partner or Company"), 'comment': fields.text('Notes'), 'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility 'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'), @@ -175,8 +174,8 @@ class res_partner(osv.osv): 'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."), 'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."), 'function': fields.char('Job Position', size=128), - 'type': fields.selection( [('default','Default'), ('invoice','Invoice'), - ('delivery','Delivery'), ('contact','Contact'), + 'type': fields.selection([('default', 'Default'), ('invoice', 'Invoice'), + ('delivery', 'Delivery'), ('contact', 'Contact'), ('other', 'Other')], 'Address Type', help="Used to select automatically the right address according to the context in sales and purchases documents."), 'street': fields.char('Street', size=128), @@ -193,25 +192,24 @@ 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"), + # image: all image fields are base64 encoded and PIL-supported '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."), + help="This field holds the image used as avatar for the partner, limited to 1024x1024px"), 'image_medium': fields.function(_get_image, fnct_inv=_set_image, string="Medium-sized image", type="binary", multi="_get_image", - store = { + 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. "\ + "resized as a 128x128px 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 = { + 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. "\ + "resized as a 64x64px 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'), @@ -230,40 +228,28 @@ class res_partner(osv.osv): if is_company: image = open(openerp.modules.get_module_resource('base', 'static/src/img', 'company_image.png')).read() else: - from PIL import Image - from StringIO import StringIO - color = (255,255,255) - if colorize: - from random import random - color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32)) - face = Image.open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png')) - avatar = Image.new('RGB', face.size) - avatar.paste(color) - avatar.paste(face, mask=face) - buffer = StringIO() - avatar.save(buffer, 'PNG') - image = buffer.getvalue() - return image.encode('base64') + image = tools.image_colorize(open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png')).read()) + return tools.image_resize_image_big(image.encode('base64')) _defaults = { 'active': True, - 'lang': lambda self, cr, uid, context: context.get('lang', 'en_US'), - 'tz': lambda self, cr, uid, context: context.get('tz', False), + 'lang': lambda self, cr, uid, ctx: ctx.get('lang', 'en_US'), + 'tz': lambda self, cr, uid, ctx: ctx.get('tz', False), 'customer': True, 'category_id': _default_category, - 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c), + 'company_id': lambda self, cr, uid, ctx: self.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=ctx), 'color': 0, 'is_company': False, 'type': 'default', 'use_parent_address': True, - 'image': lambda self, cr, uid, context: self._get_default_image(cr, uid, context.get('default_is_company', False), context), + 'image': lambda self, cr, uid, ctx: self._get_default_image(cr, uid, ctx.get('default_is_company', False), ctx), } def copy(self, cr, uid, id, default=None, context=None): if default is None: default = {} name = self.read(cr, uid, [id], ['name'], context)[0]['name'] - default.update({'name': _('%s (copy)')%(name)}) + default.update({'name': _('%s (copy)') % (name)}) return super(res_partner, self).copy(cr, uid, id, default, context) def onchange_type(self, cr, uid, ids, is_company, context=None): diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index d0f07daa004..5e1ea88f5ac 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -244,14 +244,14 @@ class res_users(osv.osv): return result _defaults = { - 'password' : '', - 'active' : True, + 'password': '', + 'active': True, 'customer': False, 'menu_id': _get_menu, 'company_id': _get_company, 'company_ids': _get_companies, 'groups_id': _get_group, - 'image': lambda self, cr, uid, context: self.pool.get('res.partner')._get_default_image(cr, uid, False, context, colorize=True), + 'image': lambda self, cr, uid, ctx={}: self.pool.get('res.partner')._get_default_image(cr, uid, False, ctx, colorize=True), } def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 170108a13ad..c1a31602262 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -86,12 +86,12 @@ class Service(object): cls._services.pop(name) def LocalService(name): - # Special case for addons support, will be removed in a few days when addons - # are updated to directly use openerp.osv.osv.service. - if name == 'object_proxy': - return openerp.osv.osv.service + # Special case for addons support, will be removed in a few days when addons + # are updated to directly use openerp.osv.osv.service. + if name == 'object_proxy': + return openerp.osv.osv.service - return Service._services[name] + return Service._services[name] class ExportService(object): """ Proxy for exported services. @@ -238,9 +238,9 @@ def init_logger(): # server intended to test it. def init_alternative_logger(): class H(logging.Handler): - def emit(self, record): - if record.levelno > 20: - print record.levelno, record.pathname, record.msg + def emit(self, record): + if record.levelno > 20: + print record.levelno, record.pathname, record.msg handler = H() # Add the handler to the 'openerp' logger. logger = logging.getLogger('openerp') diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 5cb006923c7..d81833b17a6 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -892,8 +892,8 @@ class BaseModel(object): for c in cls.__dict__.get(s, []): exist = False for c2 in range(len(new)): - #For _constraints, we should check field and methods as well - if new[c2][2]==c[2] and (new[c2][0] == c[0] \ + #For _constraints, we should check field and methods as well + if new[c2][2]==c[2] and (new[c2][0] == c[0] \ or getattr(new[c2][0],'__name__', True) == \ getattr(c[0],'__name__', False)): # If new class defines a constraint with @@ -1308,7 +1308,7 @@ class BaseModel(object): if not line[i]: continue - if field[:len(prefix)] <> prefix: + if field[:len(prefix)] != prefix: if line[i] and skip: return False continue @@ -2709,7 +2709,7 @@ class BaseModel(object): # if val is a many2one, just write the ID if type(val) == tuple: val = val[0] - if (val<>False) or (type(val)<>bool): + if val is not False: cr.execute(update_query, (ss[1](val), key)) def _check_selection_field_value(self, cr, uid, field, value, context=None): @@ -2733,7 +2733,7 @@ class BaseModel(object): elif val in dict(self._columns[field].selection(self, cr, uid, context=context)): return raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._table, field)) + _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._table, field)) def _check_removed_columns(self, cr, log=False): # iterate on the database columns to drop the NOT NULL constraints @@ -5013,66 +5013,56 @@ class BaseModel(object): return True - def resolve_o2m_commands_to_record_dicts(self, cr, uid, field_name, o2m_commands, fields=None, context=None): - """ Serializes o2m commands into record dictionaries (as if - all the o2m records came from the database via a read()), and - returns an iterable over these dictionaries. + def resolve_2many_commands(self, cr, uid, field_name, commands, fields=None, context=None): + """ Serializes one2many and many2many commands into record dictionaries + (as if all the records came from the database via a read()). This + method is aimed at onchange methods on one2many and many2many fields. - Because o2m commands might be creation commands, not all - record ids will contain an ``id`` field. Commands matching an - existing record (``UPDATE`` and ``LINK_TO``) will have an id. + Because commands might be creation commands, not all record dicts + will contain an ``id`` field. Commands matching an existing record + will have an ``id``. - .. note:: ``CREATE``, ``UPDATE`` and ``LINK_TO`` stand for the - o2m command codes ``0``, ``1`` and ``4`` - respectively - - :param field_name: name of the o2m field matching the commands - :type field_name: str - :param o2m_commands: one2many commands to execute on ``field_name`` - :type o2m_commands: list((int|False, int|False, dict|False)) - :param fields: list of fields to read from the database, when applicable - :type fields: list(str) - :raises AssertionError: if a command is not ``CREATE``, ``UPDATE`` or ``LINK_TO`` - :returns: o2m records in a shape similar to that returned by - ``read()`` (except records may be missing the ``id`` - field if they don't exist in db) - :rtype: ``list(dict)`` + :param field_name: name of the one2many or many2many field matching the commands + :type field_name: str + :param commands: one2many or many2many commands to execute on ``field_name`` + :type commands: list((int|False, int|False, dict|False)) + :param fields: list of fields to read from the database, when applicable + :type fields: list(str) + :returns: records in a shape similar to that returned by ``read()`` + (except records may be missing the ``id`` field if they don't exist in db) + :rtype: list(dict) """ - o2m_model = self._all_columns[field_name].column._obj + result = [] # result (list of dict) + record_ids = [] # ids of records to read + updates = {} # {id: dict} of updates on particular records - # convert single ids and pairs to tripled commands - commands = [] - for o2m_command in o2m_commands: - if not isinstance(o2m_command, (list, tuple)): - command = 4 - commands.append((command, o2m_command, False)) - elif len(o2m_command) == 1: - (command,) = o2m_command - commands.append((command, False, False)) - elif len(o2m_command) == 2: - command, id = o2m_command - commands.append((command, id, False)) - else: - command = o2m_command[0] - commands.append(o2m_command) - assert command in (0, 1, 4), \ - "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" + for command in commands: + if not isinstance(command, (list, tuple)): + record_ids.append(command) + elif command[0] == 0: + result.append(command[2]) + elif command[0] == 1: + record_ids.append(command[1]) + updates.setdefault(command[1], {}).update(command[2]) + elif command[0] in (2, 3): + record_ids = [id for id in record_ids if id != command[1]] + elif command[0] == 4: + record_ids.append(command[1]) + elif command[0] == 5: + result, record_ids = [], [] + elif command[0] == 6: + result, record_ids = [], list(command[2]) - # extract records to read, by id, in a mapping dict - ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] - records_by_id = dict( - (record['id'], record) - for record in self.pool.get(o2m_model).read( - cr, uid, ids_to_read, fields=fields, context=context)) + # read the records and apply the updates + other_model = self.pool.get(self._all_columns[field_name].column._obj) + for record in other_model.read(cr, uid, record_ids, fields=fields, context=context): + record.update(updates.get(record['id'], {})) + result.append(record) - record_dicts = [] - # merge record from db with record provided by command - for command, id, record in commands: - item = {} - if command in (1, 4): item.update(records_by_id[id]) - if command in (0, 1): item.update(record) - record_dicts.append(item) - return record_dicts + return result + + # for backward compatibility + resolve_o2m_commands_to_record_dicts = resolve_2many_commands # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/openerp/report/custom.py b/openerp/report/custom.py index 051d1f29862..596d9a9f1b9 100644 --- a/openerp/report/custom.py +++ b/openerp/report/custom.py @@ -21,7 +21,6 @@ import os import time -import openerp.netsvc as netsvc import openerp.tools as tools from openerp.tools.safe_eval import safe_eval as eval @@ -67,7 +66,6 @@ class report_custom(report_int): # def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None): result = [] - tmp = [] for obj in objs: tobreak = False for cond in conditions: @@ -108,7 +106,7 @@ class report_custom(report_int): key = levels.keys() for l in key: objs = eval('obj.'+l,{'obj': obj}) - if not isinstance(objs, browse_record_list) and type(objs) <> type([]): + if not isinstance(objs, (browse_record_list, list)): objs = [objs] field_new = [] cond_new = [] @@ -221,11 +219,9 @@ class report_custom(report_int): res_dic[prev].append(line) else: prev = line[groupby] - if res_dic.has_key(line[groupby]): - res_dic[line[groupby]].append(line) - else: - res_dic[line[groupby]] = [] - res_dic[line[groupby]].append(line) + res_dic.setdefault(line[groupby], []) + res_dic[line[groupby]].append(line) + #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't for key in filter(None, [x[groupby] for x in results]): row = [] @@ -267,7 +263,7 @@ class report_custom(report_int): else: try: row.append(float(r[j])) - except: + except Exception: row.append(r[j]) results2.append(row) if report['type']=='pie': @@ -365,7 +361,6 @@ class report_custom(report_int): order_date['Y'] = lambda x : x abscissa = [] - tmp = {} idx = 0 date_idx = None @@ -389,7 +384,7 @@ class report_custom(report_int): if date_idx != None: for r in results: key = process_date['Y'](r[date_idx]) - if not data_by_year.has_key(key): + if key not in data_by_year: data_by_year[key] = [] for i in range(len(r)): r[i] = fct[i](r[i]) @@ -407,14 +402,14 @@ class report_custom(report_int): for d in data_by_year[line]: for idx in range(len(fields)-1): fields_bar.append({}) - if fields_bar[idx].has_key(d[0]): + if d[0] in fields_bar[idx]: fields_bar[idx][d[0]] += d[idx+1] else: fields_bar[idx][d[0]] = d[idx+1] for idx in range(len(fields)-1): data = {} for k in fields_bar[idx].keys(): - if data.has_key(k): + if k in data: data[k] += fields_bar[idx][k] else: data[k] = fields_bar[idx][k] @@ -488,7 +483,7 @@ class report_custom(report_int): if date_idx != None: for r in results: key = process_date['Y'](r[date_idx]) - if not data_by_year.has_key(key): + if key not in data_by_year: data_by_year[key] = [] for i in range(len(r)): r[i] = fct[i](r[i]) @@ -507,14 +502,14 @@ class report_custom(report_int): for d in data_by_year[line]: for idx in range(len(fields)-1): fields_bar.append({}) - if fields_bar[idx].has_key(d[0]): + if d[0] in fields_bar[idx]: fields_bar[idx][d[0]] += d[idx+1] else: fields_bar[idx][d[0]] = d[idx+1] for idx in range(len(fields)-1): data = {} for k in fields_bar[idx].keys(): - if data.has_key(k): + if k in data: data[k] += fields_bar[idx][k] else: data[k] = fields_bar[idx][k] diff --git a/openerp/report/interface.py b/openerp/report/interface.py index 9a97c445671..17dc0973d7e 100644 --- a/openerp/report/interface.py +++ b/openerp/report/interface.py @@ -44,8 +44,8 @@ class report_int(netsvc.Service): def __init__(self, name): assert not self.exists(name), 'The report "%s" already exists!' % name super(report_int, self).__init__(name) - if name[0:7]<>'report.': - raise Exception, 'ConceptionError, bad report name, should start with "report."' + if not name.startswith('report.'): + raise Exception('ConceptionError, bad report name, should start with "report."') self.name = name self.id = 0 self.name2 = '.'.join(name.split('.')[1:]) @@ -181,7 +181,7 @@ class report_rml(report_int): def create_pdf(self, rml, localcontext = None, logo=None, title=None): if not localcontext: - localcontext={} + localcontext = {} localcontext.update({'internal_header':self.internal_header}) if logo: self.bin_datas['logo'] = logo diff --git a/openerp/report/preprocess.py b/openerp/report/preprocess.py index e681860d7a2..93b6b4d1bc9 100644 --- a/openerp/report/preprocess.py +++ b/openerp/report/preprocess.py @@ -48,7 +48,7 @@ class report(object): try: while n.tag != txt.group(3): n = n.getparent() - except: + except Exception: n = node else: n = node.getparent() diff --git a/openerp/report/print_xml.py b/openerp/report/print_xml.py index ee1b9a316b3..c2af0984c2e 100644 --- a/openerp/report/print_xml.py +++ b/openerp/report/print_xml.py @@ -200,7 +200,7 @@ class document(object): else: args = [] # get the object - if attrs.has_key('model'): + if 'model' in attrs: obj = self.pool.get(attrs['model']) else: if isinstance(browser, list): @@ -209,7 +209,7 @@ class document(object): obj = browser._table # get the ids - if attrs.has_key('ids'): + if 'ids' in attrs: ids = self.eval(browser, attrs['ids']) else: if isinstance(browser, list): @@ -228,7 +228,7 @@ class document(object): if not isinstance(datas[atr['value']], (str, unicode)): txt = str(datas[atr['value']]) else: - txt = datas[atr['value']] + txt = datas[atr['value']] el.text = txt else: for el_cld in node: diff --git a/openerp/report/printscreen/ps_form.py b/openerp/report/printscreen/ps_form.py index 8e3823e6b03..cf6d77cbe26 100644 --- a/openerp/report/printscreen/ps_form.py +++ b/openerp/report/printscreen/ps_form.py @@ -120,7 +120,7 @@ class report_printscreen_list(report_int): line[f]=round(line[f],precision) col = etree.SubElement(node_line, 'col', tree='no') if line[f] != None: - col.text = tools.ustr(line[f] or '') + col.text = tools.ustr(line[f] or '') else: col.text = '/' diff --git a/openerp/report/printscreen/ps_list.py b/openerp/report/printscreen/ps_list.py index dc280a53e3e..8f0629462f8 100644 --- a/openerp/report/printscreen/ps_list.py +++ b/openerp/report/printscreen/ps_list.py @@ -56,7 +56,7 @@ class report_printscreen_list(report_int): def _parse_string(self, view): try: dom = etree.XML(view.encode('utf-8')) - except: + except Exception: dom = etree.XML(view) return self._parse_node(dom) diff --git a/openerp/report/render/html2html/html2html.py b/openerp/report/render/html2html/html2html.py index 181c7f7ba67..09d7ad5e825 100644 --- a/openerp/report/render/html2html/html2html.py +++ b/openerp/report/render/html2html/html2html.py @@ -20,9 +20,7 @@ ############################################################################## from openerp.report.render.rml2pdf import utils -from lxml import etree import copy -import openerp.pooler as pooler import base64 import cStringIO import re diff --git a/openerp/report/render/makohtml2html/makohtml2html.py b/openerp/report/render/makohtml2html/makohtml2html.py index 57e57bd8d63..dbb3327a105 100644 --- a/openerp/report/render/makohtml2html/makohtml2html.py +++ b/openerp/report/render/makohtml2html/makohtml2html.py @@ -23,8 +23,7 @@ import mako from lxml import etree from mako.template import Template from mako.lookup import TemplateLookup -import openerp.netsvc as netsvc -import traceback, sys, os +import os _logger = logging.getLogger(__name__) @@ -126,9 +125,8 @@ class makohtml2html(object): final_html += self.format_header(etree_obj) final_html += self.format_body(etree_obj) return final_html - except Exception,e: - tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) - _logger.error('report :\n%s\n%s\n', tb_s, str(e)) + except Exception: + _logger.exception('report :') def parseNode(html, localcontext = {}): r = makohtml2html(html, localcontext) diff --git a/openerp/report/render/odt2odt/odt2odt.py b/openerp/report/render/odt2odt/odt2odt.py index d077207f4d7..b9713943dc8 100644 --- a/openerp/report/render/odt2odt/odt2odt.py +++ b/openerp/report/render/odt2odt/odt2odt.py @@ -20,7 +20,6 @@ ############################################################################## from openerp.report.render.rml2pdf import utils -from lxml import etree import copy class odt2odt(object): diff --git a/openerp/report/render/rml2html/rml2html.py b/openerp/report/render/rml2html/rml2html.py index 504492772df..90bd58af99a 100644 --- a/openerp/report/render/rml2html/rml2html.py +++ b/openerp/report/render/rml2html/rml2html.py @@ -39,7 +39,7 @@ import sys import cStringIO from lxml import etree import copy -import utils + from openerp.report.render.rml2pdf import utils class _flowable(object): @@ -98,7 +98,7 @@ class _flowable(object): try: if new_child.get('style').find('terp_tblheader')!= -1: new_node.tag = 'th' - except: + except Exception: pass process(node,new_node) if new_node.get('colWidths',False): @@ -232,7 +232,7 @@ class _rml_stylesheet(object): attr = {} attrs = ps.attrib for key, val in attrs.items(): - attr[key] = val + attr[key] = val attrs = [] for a in attr: if a in self._tags: @@ -296,13 +296,13 @@ class _rml_template(object): frames[(posy,posx,tmpl.get('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.get('width'))) for tmpl in pt.findall('pageGraphics'): for n in tmpl: - if n.tag == 'image': - self.data = rc + utils._process_text(self, n.text) - if n.tag in self._tags: - t = self._tags[n.tag](n, self.style,self.localcontext) - frames[(t.posy,t.posx,n.tag)] = t - else: - self.style.update(n) + if n.tag == 'image': + self.data = rc + utils._process_text(self, n.text) + if n.tag in self._tags: + t = self._tags[n.tag](n, self.style,self.localcontext) + frames[(t.posy,t.posx,n.tag)] = t + else: + self.style.update(n) keys = frames.keys() keys.sort() keys.reverse() diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py index 96287166074..a17c03b40cb 100644 --- a/openerp/report/render/rml2pdf/trml2pdf.py +++ b/openerp/report/render/rml2pdf/trml2pdf.py @@ -82,7 +82,7 @@ class NumberedCanvas(canvas.Canvas): def showPage(self): self._currentPage +=1 if not self._flag: - self._pageCount += 1 + self._pageCount += 1 else: self.pages.update({self._currentPage:self._pageCount}) self._codes.append({'code': self._code, 'stack': self._codeStack}) @@ -424,7 +424,7 @@ class _rml_canvas(object): flow.drawOn(self.canvas,infos['x'],infos['y']) infos['height']-=h else: - raise ValueError, "Not enough space" + raise ValueError("Not enough space") def _line_mode(self, node): ljoin = {'round':1, 'mitered':0, 'bevelled':2} diff --git a/openerp/report/render/rml2txt/rml2txt.py b/openerp/report/render/rml2txt/rml2txt.py index 4a30e0d2345..ab1930d81ba 100755 --- a/openerp/report/render/rml2txt/rml2txt.py +++ b/openerp/report/render/rml2txt/rml2txt.py @@ -22,9 +22,7 @@ import sys import StringIO -import copy from lxml import etree -import base64 import utils @@ -336,8 +334,8 @@ class _rml_stylesheet(object): attr = {} attrs = ps.attributes for i in range(attrs.length): - name = attrs.item(i).localName - attr[name] = ps.get(name) + name = attrs.item(i).localName + attr[name] = ps.get(name) attrs = [] for a in attr: if a in self._tags: diff --git a/openerp/report/render/rml2txt/utils.py b/openerp/report/render/rml2txt/utils.py index 174612d847e..a56ee02b66d 100644 --- a/openerp/report/render/rml2txt/utils.py +++ b/openerp/report/render/rml2txt/utils.py @@ -23,7 +23,6 @@ import copy import re import reportlab import reportlab.lib.units -from lxml import etree from openerp.tools.safe_eval import safe_eval as eval _regex = re.compile('\[\[(.+?)\]\]') @@ -38,7 +37,7 @@ def _child_get(node, self=None, tagname=None): if n.get('rml_except', False): try: eval(n.get('rml_except'), {}, self.localcontext) - except: + except Exception: continue if n.get('rml_tag'): try: @@ -47,7 +46,7 @@ def _child_get(node, self=None, tagname=None): n2.tag = tag n2.attrib.update(attr) yield n2 - except: + except Exception: yield n else: yield n @@ -56,7 +55,7 @@ def _child_get(node, self=None, tagname=None): if self and self.localcontext and n.get('rml_except', False): try: eval(n.get('rml_except'), {}, self.localcontext) - except: + except Exception: continue if (tagname is None) or (n.tag==tagname): yield n @@ -74,11 +73,11 @@ def _process_text(self, txt): if sps: try: txt2 = eval(sps.pop(0),self.localcontext) - except: + except Exception: txt2 = '' - if type(txt2) == type(0) or type(txt2) == type(0.0): + if isinstance(txt2, (int, float)): txt2 = str(txt2) - if type(txt2)==type('') or type(txt2)==type(u''): + if isinstance(txt2, basestring): result += txt2 return result diff --git a/openerp/report/render/simple.py b/openerp/report/render/simple.py index 4ce10991924..cb3831c141f 100644 --- a/openerp/report/render/simple.py +++ b/openerp/report/render/simple.py @@ -66,7 +66,6 @@ class simple(render.render): return self.result.getvalue() if __name__=='__main__': - import time s = simple() s.xml = '''<test> <author-list> @@ -82,7 +81,7 @@ if __name__=='__main__': </author-list> </test>''' if s.render(): - print s.get() + print s.get() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py index 07e31ea9c7d..63e0d08fc92 100644 --- a/openerp/service/http_server.py +++ b/openerp/service/http_server.py @@ -44,10 +44,8 @@ import posixpath import urllib import os import logging -from SimpleXMLRPCServer import SimpleXMLRPCDispatcher from websrv_lib import * -import openerp.netsvc as netsvc import openerp.tools as tools try: diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py index 325288791f7..fcbfaf0ba82 100644 --- a/openerp/service/netrpc_server.py +++ b/openerp/service/netrpc_server.py @@ -126,12 +126,12 @@ class TinySocketServerThread(threading.Thread,netsvc.Server): ct.start() lt = len(self.threads) if (lt > 10) and (lt % 10 == 0): - # Not many threads should be serving at the same time, so log - # their abuse. - _logger.debug("Netrpc: %d threads", len(self.threads)) + # Not many threads should be serving at the same time, so log + # their abuse. + _logger.debug("Netrpc: %d threads", len(self.threads)) self.socket.close() except Exception, e: - _logger.warning("Netrpc: closing because of exception %s" % str(e)) + _logger.warning("Netrpc: closing because of exception %s", e) self.socket.close() return False diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index d8a7c663dbe..5bc4eb7b9aa 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -238,9 +238,9 @@ class db(netsvc.ExportService): if not data or res: _logger.error( - 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '\ - 'It should be provided as a -w <PASSWD> command-line option, or as `db_password` in the '\ - 'server configuration file.\n %s' % (db_name, data)) + 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. ' + 'It should be provided as a -w <PASSWD> command-line option, or as `db_password` in the ' + 'server configuration file.\n %s', db_name, data) raise Exception, "Couldn't dump database" _logger.info('DUMP DB successful: %s', db_name) @@ -253,7 +253,7 @@ class db(netsvc.ExportService): self._set_pg_psw_env_var() if self.exp_db_exist(db_name): - _logger.warning('RESTORE DB: %s already exists' % (db_name,)) + _logger.warning('RESTORE DB: %s already exists', db_name) raise Exception, "Database already exists" self._create_empty_database(db_name) @@ -282,7 +282,7 @@ class db(netsvc.ExportService): res = stdout.close() if res: raise Exception, "Couldn't restore database" - _logger.info('RESTORE DB: %s' % (db_name)) + _logger.info('RESTORE DB: %s', db_name) return True finally: @@ -465,8 +465,7 @@ GNU Public Licence. backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M')) if zips and not os.path.isdir(backup_directory): - _logger.info('create a new backup directory to \ - store the old modules: %s', backup_directory) + _logger.info('create a new backup directory to store the old modules: %s', backup_directory) os.makedirs(backup_directory) for module in zips: @@ -526,11 +525,11 @@ GNU Public Licence. 'OS Name : %s\n' \ %(platform.platform(), platform.os.name) if os.name == 'posix': - if platform.system() == 'Linux': - lsbinfo = os.popen('lsb_release -a').read() - environment += '%s'%(lsbinfo) - else: - environment += 'Your System is not lsb compliant\n' + if platform.system() == 'Linux': + lsbinfo = os.popen('lsb_release -a').read() + environment += '%s'%(lsbinfo) + else: + environment += 'Your System is not lsb compliant\n' environment += 'Operating System Release : %s\n' \ 'Operating System Version : %s\n' \ 'Operating System Architecture : %s\n' \ @@ -695,7 +694,7 @@ class report_spool(netsvc.ExportService): self._reports[id]['state'] = True except Exception, exception: - _logger.exception('Exception: %s\n', str(exception)) + _logger.exception('Exception: %s\n', exception) if hasattr(exception, 'name') and hasattr(exception, 'value'): self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) else: @@ -732,7 +731,7 @@ class report_spool(netsvc.ExportService): self._reports[id]['format'] = format self._reports[id]['state'] = True except Exception, exception: - _logger.exception('Exception: %s\n', str(exception)) + _logger.exception('Exception: %s\n', exception) if hasattr(exception, 'name') and hasattr(exception, 'value'): self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) else: diff --git a/openerp/tests/addons/test_impex/tests/test_import.py b/openerp/tests/addons/test_impex/tests/test_import.py index 4712d53feff..03507ee7012 100644 --- a/openerp/tests/addons/test_impex/tests/test_import.py +++ b/openerp/tests/addons/test_impex/tests/test_import.py @@ -716,6 +716,15 @@ class test_m2m(ImporterCase): class test_o2m(ImporterCase): model_name = 'export.one2many' + def test_name_get(self): + # FIXME: bloody hell why can't this just name_create the record? + self.assertRaises( + IndexError, + self.import_, + ['const', 'value'], + [['5', u'Java is a DSL for taking large XML files' + u' and converting them to stack traces']]) + def test_single(self): self.assertEqual( self.import_(['const', 'value/value'], [ diff --git a/openerp/tests/addons/test_limits/models.py b/openerp/tests/addons/test_limits/models.py index 5240acd23ab..32e2a9a16f4 100644 --- a/openerp/tests/addons/test_limits/models.py +++ b/openerp/tests/addons/test_limits/models.py @@ -27,7 +27,6 @@ class m(openerp.osv.osv.Model): return True def consume_cpu_time(self, cr, uid, seconds, context=None): - import os t0 = time.clock() t1 = time.clock() while t1 - t0 < seconds: diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index ea9b37e5f3b..bce5bc3903a 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -1,4 +1,3 @@ -import os import unittest2 import openerp @@ -15,6 +14,10 @@ LINK_TO = lambda id: (4, id, False) DELETE_ALL = lambda: (5, False, False) REPLACE_WITH = lambda ids: (6, False, ids) +def sorted_by_id(list_of_dicts): + "sort dictionaries by their 'id' field; useful for comparisons" + return sorted(list_of_dicts, key=lambda d: d.get('id')) + class TestO2MSerialization(common.TransactionCase): def setUp(self): @@ -23,21 +26,18 @@ class TestO2MSerialization(common.TransactionCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', []) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', - map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}])) - self.assertEqual(results, [ - {'foo': 'bar'}, - {'foo': 'baz'}, - {'foo': 'baq'} - ]) + values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}] + results = self.partner.resolve_2many_commands( + self.cr, UID, 'address', map(CREATE, values)) + + self.assertEqual(results, values) def test_LINK_TO_command(self): " reads the records from the database, records are returned with their ids. " @@ -48,14 +48,14 @@ class TestO2MSerialization(common.TransactionCase): ] commands = map(LINK_TO, ids) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', commands, ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': ids[0], 'name': 'foo'}, {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} - ]) + ])) def test_bare_ids_command(self): " same as the equivalent LINK_TO commands " @@ -65,14 +65,14 @@ class TestO2MSerialization(common.TransactionCase): self.partner.create(self.cr, UID, {'name': 'baz'}) ] - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', ids, ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': ids[0], 'name': 'foo'}, {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} - ]) + ])) def test_UPDATE_command(self): " take the in-db records and merge the provided information in " @@ -80,18 +80,32 @@ class TestO2MSerialization(common.TransactionCase): id_bar = self.partner.create(self.cr, UID, {'name': 'bar'}) id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), UPDATE(id_baz, {'name': 'quux'}) ], ['name', 'city']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': id_foo, 'name': 'foo', 'city': False}, {'id': id_bar, 'name': 'qux', 'city': 'tagtag'}, {'id': id_baz, 'name': 'quux', 'city': 'tag'} - ]) + ])) + + def test_DELETE_command(self): + " deleted records are not returned at all. " + ids = [ + self.partner.create(self.cr, UID, {'name': 'foo'}), + self.partner.create(self.cr, UID, {'name': 'bar'}), + self.partner.create(self.cr, UID, {'name': 'baz'}) + ] + commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])] + + results = self.partner.resolve_2many_commands( + self.cr, UID, 'address', commands, ['name']) + + self.assertEqual(results, []) def test_mixed_commands(self): ids = [ @@ -99,28 +113,27 @@ class TestO2MSerialization(common.TransactionCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), LINK_TO(ids[1]), - LINK_TO(ids[2]), + DELETE(ids[2]), UPDATE(ids[3], {'name': 'quux',}), UPDATE(ids[4], {'name': 'corge'}), CREATE({'name': 'grault'}), LINK_TO(ids[5]) ], ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'name': 'foo'}, {'id': ids[0], 'name': 'bar'}, {'id': ids[1], 'name': 'baz'}, - {'id': ids[2], 'name': 'qux'}, {'id': ids[3], 'name': 'quux'}, {'id': ids[4], 'name': 'corge'}, {'name': 'grault'}, {'id': ids[5], 'name': 'garply'} - ]) + ])) def test_LINK_TO_pairs(self): "LINK_TO commands can be written as pairs, instead of triplets" @@ -131,43 +144,20 @@ class TestO2MSerialization(common.TransactionCase): ] commands = map(lambda id: (4, id), ids) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', commands, ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': ids[0], 'name': 'foo'}, {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} - ]) + ])) def test_singleton_commands(self): "DELETE_ALL can appear as a singleton" + results = self.partner.resolve_2many_commands( + self.cr, UID, 'address', [DELETE_ALL()], ['name']) - try: - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [(5,)], ['name']) - except AssertionError: - # 5 should fail with an assert error, but not e.g. a ValueError - pass - - def test_invalid_commands(self): - "Commands with uncertain semantics in this context should be forbidden" - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE(42)], ['name']) - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [FORGET(42)], ['name']) - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE_ALL()], ['name']) - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [REPLACE_WITH([42])], ['name']) - + self.assertEqual(results, []) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tiny_socket.py b/openerp/tiny_socket.py index b4bed83a4c0..b5f5bbccee3 100644 --- a/openerp/tiny_socket.py +++ b/openerp/tiny_socket.py @@ -22,12 +22,15 @@ import socket import cPickle import cStringIO -import marshal import netsvc #.apidoc title: Net-RPC classes +# Pickle protocol version 2 is optimized compared to default (version 0) +PICKLE_PROTOCOL = 2 + + class Myexception(Exception): """ custom exception object store @@ -63,20 +66,19 @@ class mysocket: netsvc.close_socket(self.sock) def mysend(self, msg, exception=False, traceback=None): - msg = cPickle.dumps([msg,traceback]) - self.sock.sendall('%8d%s%s' % (len(msg), exception and "1" or "0", msg)) + msg = cPickle.dumps([msg, traceback], PICKLE_PROTOCOL) + self.sock.sendall('%8d%d%s' % (len(msg), bool(exception), msg)) def myreceive(self): buf='' - while len(buf) < 8: - chunk = self.sock.recv(8 - len(buf)) + while len(buf) < 9: + chunk = self.sock.recv(9 - len(buf)) if not chunk: raise socket.timeout buf += chunk - size = int(buf) - buf = self.sock.recv(1) - if buf != "0": - exception = buf + size = int(buf[:8]) + if buf[8] != "0": + exception = buf[8] else: exception = False msg = '' diff --git a/openerp/tools/image.py b/openerp/tools/image.py index 1caafb23a18..f673906f328 100644 --- a/openerp/tools/image.py +++ b/openerp/tools/image.py @@ -20,9 +20,12 @@ ############################################################################## import io -from PIL import Image import StringIO +from PIL import Image +from PIL import ImageFilter +from random import random + # ---------------------------------------- # Image resizing # ---------------------------------------- @@ -47,15 +50,17 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file image. - past the thumbnail on the transparent background and center it. - + :param base64_source: base64-encoded version of the source - image + image; if False, returns False :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. """ + if not base64_source: + return False 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 @@ -63,6 +68,7 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file return base64_source # create a thumbnail: will resize and keep ratios image.thumbnail(size, Image.ANTIALIAS) + image = image.filter(ImageFilter.SHARPEN) # create a transparent image for background background = Image.new('RGBA', size, (255, 255, 255, 0)) # past the resized image on the background @@ -72,42 +78,57 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file 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'): +def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG', avoid_if_small=True): """ 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. + :param size, encoding, filetype, avoid_if_small: refer to image_resize_image """ - if not base64_source: - return False - return image_resize_image(base64_source, size, encoding, filetype, True) + return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small) -def image_resize_image_medium(base64_source, size=(180, 180), encoding='base64', filetype='PNG'): +def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype='PNG', avoid_if_small=False): """ 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. + :param size, encoding, filetype, avoid_if_small: refer to image_resize_image """ - 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'): + return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small) + +def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype='PNG', avoid_if_small=False): """ 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. + :param size, encoding, filetype, avoid_if_small: refer to image_resize_image """ - if not base64_source: - return False - return image_resize_image(base64_source, size, encoding, filetype) + return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small) + +# ---------------------------------------- +# Colors +# --------------------------------------- + +def image_colorize(original, randomize=True, color=(255, 255, 255)): + """ Add a color to the transparent background of an image. + :param original: file object on the original image file + :param randomize: randomize the background color + :param color: background-color, if not randomize + """ + # create a new image, based on the original one + original = Image.open(io.BytesIO(original)) + image = Image.new('RGB', original.size) + # generate the background color, past it as background + if randomize: + color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32)) + image.paste(color) + image.paste(original, mask=original) + # return the new image + buffer = StringIO.StringIO() + image.save(buffer, 'PNG') + return buffer.getvalue() # ---------------------------------------- # 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'): + big_name='image', medium_name='image_medium', small_name='image_small', + avoid_resize_big=True, avoid_resize_medium=False, avoid_resize_small=False): """ 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 @@ -116,24 +137,22 @@ def image_get_resized_images(base64_source, return_big=False, return_medium=True 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. + + :param base64_source: base64-encoded version of the source + image; if False, all returnes values will be False + :param return_{..}: if set, computes and return the related resizing + of the image + :param {..}_name: key of the resized image in the return dictionary; + 'image', 'image_medium' and 'image_small' by default. + :param avoid_resize_[..]: see avoid_if_small parameter :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 \ No newline at end of file + if return_big: + return_dict[big_name] = image_resize_image_big(base64_source, avoid_if_small=avoid_resize_big) + if return_medium: + return_dict[medium_name] = image_resize_image_medium(base64_source, avoid_if_small=avoid_resize_medium) + if return_small: + return_dict[small_name] = image_resize_image_small(base64_source, avoid_if_small=avoid_resize_small) + return return_dict diff --git a/openerp/wizard/__init__.py b/openerp/wizard/__init__.py index 7354a43498d..debf0f1e74f 100644 --- a/openerp/wizard/__init__.py +++ b/openerp/wizard/__init__.py @@ -31,7 +31,6 @@ import openerp.pooler as pooler from openerp.osv.osv import except_osv from openerp.osv.orm import except_orm -import sys _logger = logging.getLogger(__name__) @@ -168,10 +167,7 @@ class interface(netsvc.Service): or isinstance(e, except_orm): netsvc.abort_response(2, e.name, 'warning', e.value) else: - import traceback - tb_s = reduce(lambda x, y: x+y, traceback.format_exception( - sys.exc_type, sys.exc_value, sys.exc_traceback)) - _logger.error('Exception in call: ' + tb_s) + _logger.exception('Exception in call:') raise return res