[MERGE] from trunk

bzr revid: rco@openerp.com-20120403082743-7lf3rb6h0ff95w7f
This commit is contained in:
Raphael Collet 2012-04-03 10:27:43 +02:00
commit 1a3a33b6d5
56 changed files with 3422 additions and 1879 deletions

View File

@ -0,0 +1,9 @@
User avatar
===========
This revision adds an avatar for users. This replaces the use of gravatar to emulate avatars, used in views like the tasks kanban view. Two fields have been added to the res.users model:
- avatar_big, a binary field holding the image. It is base-64 encoded, and PIL-supported. Images stored are resized to 540x450 px, to limitate the binary field size.
- avatar, a function binary field holding an automatically resized version of the avatar_big field. It is also base-64 encoded, and PIL-supported. Dimensions of the resized avatar are 180x150. This field is used as an inteface to get and set the user avatar.
When changing the avatar through the avatar function field, the new image is automatically resized to 540x450, and stored in the avatar_big field. This triggers the function field, that will compute a 180x150 resized version of the image.
An avatar field has been added to the users form view, as well as in Preferences. When creating a new user, a default avatar is chosen among 6 possible default images.

View File

@ -6,3 +6,11 @@ OpenERP Server
:maxdepth: 1
test-framework
New feature merges
++++++++++++++++++
.. toctree::
:maxdepth: 1
api/user_img_specs

View File

@ -75,6 +75,7 @@
'security/base_security.xml',
'publisher_warranty/publisher_warranty_view.xml',
'security/ir.model.access.csv',
'security/ir.model.access-1.csv', # res.partner.address is deprecated; it is still there for backward compability only and will be removed in next version
'res/res_widget_view.xml',
'res/res_widget_data.xml',
'publisher_warranty/publisher_warranty_data.xml',

View File

@ -1058,14 +1058,9 @@
<record id="main_partner" model="res.partner">
<field name="name">Your Company</field>
<!-- Address and Company ID will be set later -->
<field name="address" eval="[]"/>
<field name="company_id" eval="None"/>
<field name="customer" eval="False"/>
</record>
<record id="main_address" model="res.partner.address">
<field name="partner_id" ref="main_partner"/>
<field name="type">default</field>
<field name="company_id" eval="None"/>
<field name="is_company" eval="True"/>
</record>
<!-- Currencies -->
@ -1102,9 +1097,6 @@
<record id="main_partner" model="res.partner">
<field name="company_id" ref="main_company"/>
</record>
<record id="main_address" model="res.partner.address">
<field name="company_id" ref="main_company"/>
</record>
<record id="EUR" model="res.currency">
<field name="company_id" ref="main_company"/>
</record>
@ -1373,7 +1365,7 @@
<record id="INR" model="res.currency">
<field name="name">INR</field>
<field name="symbol">Rs</field>
<field name="symbol"></field>
<field name="rounding">0.01</field>
<field name="accuracy">4</field>
<field name="company_id" ref="main_company"/>

View File

@ -2,8 +2,8 @@
<openerp>
<data noupdate="1">
<record id="demo_address" model="res.partner.address">
<field name="name">Fabien Dupont</field>
<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>

View File

@ -27,8 +27,10 @@
parent="base.menu_custom" name="Reporting" sequence="30"
/>
<menuitem id="base.menu_reporting" name="Reporting" parent="base.menu_administration" sequence="11"
groups="base.group_extended"/>
<menuitem id="menu_audit" name="Audit" parent="base.menu_reporting" sequence="50"/>
<menuitem id="base.menu_reporting" name="Reporting" sequence="45"/>
<menuitem id="base.menu_reporting_dashboard" name="Dashboards" sequence="0" parent="base.menu_reporting" groups="base.group_extended"/>
<menuitem id="menu_audit" name="Audit" parent="base.menu_reporting" sequence="50" groups="base.group_system"/>
<menuitem id="base.menu_reporting_config" name="Configuration" parent="base.menu_reporting" sequence="100" groups="base.group_system"/>
</data>
</openerp>

View File

@ -84,12 +84,18 @@
<form string="Users">
<field name="name" readonly="1"/>
<newline/>
<group colspan="2" col="2">
<separator string="Preferences" colspan="2"/>
<field name="view" readonly="0"/>
<field name="context_lang" readonly="0"/>
<field name="context_tz" readonly="0"/>
<field name="menu_tips" readonly="0" groups="base.group_no_one"/>
<group colspan="2" col="3">
<group col="2" colspan="2">
<separator string="Preferences" colspan="2"/>
<field name="view" readonly="0"/>
<field name="context_lang" readonly="0"/>
<field name="context_tz" readonly="0"/>
<field name="menu_tips" readonly="0" groups="base.group_no_one"/>
</group>
<group col="2" colspan="1">
<separator string="Avatar" colspan="2"/>
<field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)"/>
</group>
</group>
<group name="default_filters" colspan="2" col="2">
<separator string="Default Filters" colspan="2"/>
@ -118,32 +124,36 @@
</group>
<notebook colspan="4">
<page string="User">
<group colspan="4" col="6">
<!-- Second nested group to avoid misalignment with email prefs groups
in simplified view -->
<group colspan="6" col="6">
<group col="2" colspan="2">
<separator string="Preferences" colspan="2"/>
<field name="context_lang"/>
<field name="context_tz"/>
<field name="menu_tips"/>
<group colspan="4" col="7">
<!-- Second nested group to avoid misalignment with email prefs groups
in simplified view -->
<group colspan="7" col="7">
<group col="2" colspan="1">
<separator string="Avatar" colspan="2"/>
<field name="avatar" widget='image' nolabel="1" colspan="2" on_change="onchange_avatar(avatar)"/>
</group>
<group col="3" colspan="2">
<separator string="Preferences" colspan="3"/>
<field name="context_lang"/>
<field name="context_tz"/>
<field name="menu_tips"/>
</group>
<group name="default_filters" colspan="2" col="2">
<separator string="Default Filters" colspan="2"/>
<field name="company_id" required="1" context="{'user_preference': 0}" groups="base.group_multi_company"/>
</group>
<group colspan="2" col="2" groups="base.group_extended">
<separator string="Action" colspan="2"/>
<field name="action_id"/>
<field domain="[('usage','=','menu')]" name="menu_id" required="True"/>
</group>
</group>
<group name="default_filters" colspan="2" col="2">
<separator string="Default Filters" colspan="2"/>
<field name="company_id" required="1" context="{'user_preference': 0}" groups="base.group_multi_company"/>
</group>
<group colspan="2" col="2" groups="base.group_extended">
<separator string="Action" colspan="2"/>
<field name="action_id"/>
<field domain="[('usage','=','menu')]" name="menu_id" required="True"/>
<group colspan="7" col="2">
<separator string="Email Preferences" colspan="2"/>
<field name="user_email" widget="email"/>
<field name="signature"/>
</group>
</group>
<group colspan="6" col="2">
<separator string="Email Preferences" colspan="2"/>
<field name="user_email" widget="email"/>
<field name="signature"/>
</group>
</group>
</page>
<page string="Access Rights">
<field nolabel="1" name="groups_id"/>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:44+0000\n"
"PO-Revision-Date: 2012-01-08 15:06+0000\n"
"Last-Translator: kifcaliph <Unknown>\n"
"PO-Revision-Date: 2012-03-19 10:06+0000\n"
"Last-Translator: Abdulwhhab A. Al-Shehri <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-03-10 04:46+0000\n"
"X-Generator: Launchpad (build 14914)\n"
"X-Launchpad-Export-Date: 2012-03-20 04:55+0000\n"
"X-Generator: Launchpad (build 14969)\n"
#. module: base
#: model:res.country,name:base.sh
@ -367,7 +367,7 @@ msgstr "اسم المعالج"
#. module: base
#: model:res.groups,name:base.group_partner_manager
msgid "Partner Manager"
msgstr ""
msgstr "إدارة الشركاء"
#. module: base
#: model:ir.module.category,name:base.module_category_customer_relationship_management
@ -388,7 +388,7 @@ msgstr "تجميع غير ممكن"
#. module: base
#: field:ir.module.category,child_ids:0
msgid "Child Applications"
msgstr ""
msgstr "التطبيقات الفرعية"
#. module: base
#: field:res.partner,credit_limit:0
@ -408,7 +408,7 @@ msgstr "تاريخ التحديث"
#. module: base
#: model:ir.module.module,shortdesc:base.module_base_action_rule
msgid "Automated Action Rules"
msgstr ""
msgstr "قواعد الاحداث التلقائية"
#. module: base
#: view:ir.attachment:0
@ -473,11 +473,13 @@ msgid ""
"The user this filter is available to. When left empty the filter is usable "
"by the system only."
msgstr ""
"مرشحات المستخدم غير متوفرة. عندما يترك فارغا فان المرشح يستخدم بواسطة النظام "
"فقط."
#. module: base
#: help:res.partner,website:0
msgid "Website of Partner."
msgstr ""
msgstr "موقع إلكتروني للشريك."
#. module: base
#: help:ir.actions.act_window,views:0
@ -506,7 +508,7 @@ msgstr "تنسيق التاريخ"
#. module: base
#: model:ir.module.module,shortdesc:base.module_base_report_designer
msgid "OpenOffice Report Designer"
msgstr ""
msgstr "مصمم تقارير اوبن اوفيس"
#. module: base
#: field:res.bank,email:0
@ -566,7 +568,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_layout
msgid "Sales Orders Print Layout"
msgstr ""
msgstr "تنسيق طباعة اوامر البيع"
#. module: base
#: selection:base.language.install,lang:0
@ -576,7 +578,7 @@ msgstr "الأسبانية"
#. 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
@ -700,7 +702,7 @@ msgstr "تمّت عملية التصدير"
#. module: base
#: model:ir.module.module,shortdesc:base.module_plugin_outlook
msgid "Outlook Plug-In"
msgstr ""
msgstr "إضافات الاوت لوك"
#. module: base
#: view:ir.model:0
@ -727,7 +729,7 @@ msgstr "الأردن"
#. module: base
#: help:ir.cron,nextcall:0
msgid "Next planned execution date for this job."
msgstr ""
msgstr "تاريخ الحدث المقرر لاحقا لهذه الوظيفة."
#. module: base
#: code:addons/base/ir/ir_model.py:139
@ -743,7 +745,7 @@ msgstr "إريتريا"
#. module: base
#: sql_constraint:res.company:0
msgid "The company name must be unique !"
msgstr ""
msgstr "اسم الشركة يجب أن يكون فريداً !"
#. module: base
#: view:res.config:0
@ -778,7 +780,7 @@ msgstr ""
#. module: base
#: view:ir.mail_server:0
msgid "Security and Authentication"
msgstr ""
msgstr "الحماية و التحقق من الصلاحيات"
#. module: base
#: view:base.language.export:0
@ -879,7 +881,7 @@ msgstr ""
#. module: base
#: view:res.users:0
msgid "Email Preferences"
msgstr ""
msgstr "تفضيلات البريد الالكتروني"
#. module: base
#: model:ir.module.module,description:base.module_audittrail
@ -971,7 +973,7 @@ msgstr "نييوي"
#. module: base
#: model:ir.module.module,shortdesc:base.module_membership
msgid "Membership Management"
msgstr ""
msgstr "إدارة الإشتراكات"
#. module: base
#: selection:ir.module.module,license:0
@ -998,7 +1000,7 @@ msgstr "أنواع مراجع الطلبات"
#. module: base
#: model:ir.module.module,shortdesc:base.module_google_base_account
msgid "Google Users"
msgstr ""
msgstr "مستخدمي جوجل"
#. module: base
#: help:ir.server.object.lines,value:0
@ -1035,6 +1037,7 @@ msgstr "ملف TGZ"
msgid ""
"Users added to this group are automatically added in the following groups."
msgstr ""
"الاشخاص المضافون الى هذه المجموعة أضيفوا تلقائيا الى المجموعات التالية."
#. module: base
#: view:res.lang:0
@ -1088,7 +1091,7 @@ msgstr "لا يمكن استخدام كلمات مرور فارغة لأسباب
#: code:addons/base/ir/ir_mail_server.py:192
#, python-format
msgid "Connection test failed!"
msgstr ""
msgstr "فشلت محاولة الاتصال!"
#. module: base
#: selection:ir.actions.server,state:0
@ -1209,7 +1212,7 @@ msgstr "الأسبانية / Español (GT)"
#. module: base
#: field:ir.mail_server,smtp_port:0
msgid "SMTP Port"
msgstr ""
msgstr "المنفذ SMTP"
#. module: base
#: model:ir.module.module,shortdesc:base.module_import_sugarcrm
@ -1231,7 +1234,7 @@ msgstr ""
#: code:addons/base/module/wizard/base_language_install.py:55
#, python-format
msgid "Language Pack"
msgstr ""
msgstr "حزمة لغة"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_tests
@ -1289,11 +1292,14 @@ msgid ""
"reference it\n"
"- creation/update: a mandatory field is not correctly set"
msgstr ""
"لا يمكن إكمال العملية، ربما بسبب أحد الاسباب التالية:\n"
"-الحذف: ربما تكون تحاول حذف سجل بينما هناك سجلات اخرى تشير اليه.\n"
"الانشاء/التحديث: حقل أساسي لم يتم ادخاله بشكل صحيح."
#. module: base
#: field:ir.module.category,parent_id:0
msgid "Parent Application"
msgstr ""
msgstr "التطبيق الرئيسي"
#. module: base
#: code:addons/base/res/res_users.py:222
@ -1310,12 +1316,12 @@ msgstr "لتصدير لغة جديدة، لا تختر أي لغة."
#: model:ir.module.module,shortdesc:base.module_document
#: model:ir.module.module,shortdesc:base.module_knowledge
msgid "Document Management System"
msgstr ""
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
@ -1340,6 +1346,9 @@ msgid ""
"use the accounting application of OpenERP, journals and accounts will be "
"created automatically based on these data."
msgstr ""
"قم بتكوين الحسابات البنكية لشركتك و اختر ما تريده ان يظهر في اسفل التقارير. "
"بإمكانك إعادة ترتيب الحسابات من قائمة العرض. إذا كنت تستخدم ملحق الحسابات, "
"سيتم انشاء اليوميات و الحسابات تلقائيا اعتمادا على هذه البيانات."
#. module: base
#: view:ir.module.module:0
@ -1359,6 +1368,14 @@ msgid ""
" * Commitment Date\n"
" * Effective Date\n"
msgstr ""
"\n"
"أضف تواريخ اضافية الى طلب البيع.\n"
"===============================\n"
"\n"
"يمكنك اضافة التواريخ التالية الى طلب البيع:\n"
"* التاريخ المطلوب\n"
"* التاريخ الملتزم به\n"
"* تاريخ السريان\n"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_sequence
@ -1804,7 +1821,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_hr_evaluation
msgid "Employee Appraisals"
msgstr ""
msgstr "تقييمات الموظف"
#. module: base
#: selection:ir.actions.server,state:0
@ -1853,7 +1870,7 @@ msgstr "نموذج ملحق"
#. module: base
#: field:res.partner.bank,footer:0
msgid "Display on Reports"
msgstr ""
msgstr "عرض على التقارير"
#. module: base
#: model:ir.module.module,description:base.module_l10n_cn
@ -2050,7 +2067,7 @@ msgstr "وضع العرض"
msgid ""
"Display this bank account on the footer of printed documents like invoices "
"and sales orders."
msgstr ""
msgstr "عرض هذا الحساب البنكي على أسفل المطبوعات مثل الفواتير وطلبات البيع."
#. module: base
#: view:base.language.import:0
@ -2159,7 +2176,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_subscription
msgid "Recurring Documents"
msgstr ""
msgstr "وثائق متكررة"
#. module: base
#: model:res.country,name:base.bs
@ -2244,7 +2261,7 @@ msgstr "المجموعات"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (CL) / Español (CL)"
msgstr ""
msgstr "الأسبانية / Español (CL)"
#. module: base
#: model:res.country,name:base.bz
@ -2517,7 +2534,7 @@ msgstr "استيراد / تصدير"
#. module: base
#: model:ir.actions.todo.category,name:base.category_tools_customization_config
msgid "Tools / Customization"
msgstr ""
msgstr "أدوات / تخصيصات"
#. module: base
#: field:ir.model.data,res_id:0
@ -2533,7 +2550,7 @@ msgstr "عنوان البريد الإلكتروني"
#. module: base
#: selection:base.language.install,lang:0
msgid "French (BE) / Français (BE)"
msgstr ""
msgstr "الفرنسية / Français (BE)"
#. module: base
#: view:ir.actions.server:0
@ -2768,7 +2785,7 @@ msgstr "جزيرة نورفولك"
#. module: base
#: selection:base.language.install,lang:0
msgid "Korean (KR) / 한국어 (KR)"
msgstr ""
msgstr "الكورية / 한국어 (KR)"
#. module: base
#: help:ir.model.fields,model:0
@ -3218,7 +3235,7 @@ msgstr ""
#. module: base
#: selection:base.language.install,lang:0
msgid "Finnish / Suomi"
msgstr ""
msgstr "الفنلندية / Suomi"
#. module: base
#: field:ir.rule,perm_write:0
@ -3233,7 +3250,7 @@ msgstr "اللقب"
#. module: base
#: selection:base.language.install,lang:0
msgid "German / Deutsch"
msgstr ""
msgstr "الألمانية / Deutsch"
#. module: base
#: view:ir.actions.server:0
@ -3581,7 +3598,7 @@ msgstr ""
#. 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
@ -3611,6 +3628,8 @@ msgid ""
"Value Added Tax number. Check the box if the partner is subjected to the "
"VAT. Used by the VAT legal statement."
msgstr ""
"رقم ضريبة القيمة المضافة. ضع علامة في هذا المربع اذا كان الشريك خاضع لضريبة "
"القيمة المضافة. تستخدم بواسطة كشف ضريبة القيمة المضافة القانونية."
#. module: base
#: selection:ir.sequence,implementation:0
@ -3708,7 +3727,7 @@ msgstr "ضريبة القيمة المضافة"
#. module: base
#: field:res.users,new_password:0
msgid "Set password"
msgstr ""
msgstr "ضبط كلمة المرور"
#. module: base
#: view:res.lang:0
@ -3723,7 +3742,7 @@ msgstr "خطأ! لا يمكنك إنشاء فئات متداخلة."
#. module: base
#: view:res.lang:0
msgid "%x - Appropriate date representation."
msgstr ""
msgstr "%x - صيغة التاريخ المناسب"
#. module: base
#: model:ir.module.module,description:base.module_web_mobile
@ -3836,7 +3855,7 @@ msgstr ""
#. 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
@ -3957,7 +3976,7 @@ msgstr "البرتغال"
#. module: base
#: model:ir.module.module,shortdesc:base.module_share
msgid "Share any Document"
msgstr ""
msgstr "مشاركة اي مستند"
#. module: base
#: field:ir.module.module,certificate:0
@ -4235,6 +4254,8 @@ msgid ""
"When no specific mail server is requested for a mail, the highest priority "
"one is used. Default priority is 10 (smaller number = higher priority)"
msgstr ""
"حين إستداعاء البريد من الخادم، يتم اختيار الملف الملقم حسب الاولوية.\r\n"
"القيمة الافتراضية هي 10 (الارقام الاصغر= اولوية أعلى)"
#. module: base
#: model:ir.module.module,description:base.module_crm_partner_assign
@ -4291,7 +4312,7 @@ msgstr "\"كود\" لابد أن يكون فريداً"
#. module: base
#: model:ir.module.module,shortdesc:base.module_hr_expense
msgid "Expenses Management"
msgstr ""
msgstr "إدارة المصروفات و النفقات"
#. module: base
#: view:workflow.activity:0
@ -4438,7 +4459,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
@ -4613,7 +4634,7 @@ msgstr "ليسوتو"
#. module: base
#: model:ir.module.module,shortdesc:base.module_base_vat
msgid "VAT Number Validation"
msgstr ""
msgstr "التحقق من رقم ضريبة القيمة المضافة"
#. module: base
#: model:ir.module.module,shortdesc:base.module_crm_partner_assign
@ -4759,7 +4780,7 @@ msgstr "قيمة لاحقة من السجل للمسلسل"
#. module: base
#: help:ir.mail_server,smtp_user:0
msgid "Optional username for SMTP authentication"
msgstr ""
msgstr "اختياري: اسم المستخدم للتحقق من قبل ملقم البريد"
#. module: base
#: model:ir.model,name:base.model_ir_actions_actions
@ -4844,7 +4865,7 @@ msgstr ""
#. module: base
#: help:res.partner.bank,company_id:0
msgid "Only if this bank account belong to your company"
msgstr ""
msgstr "فقط إذا كان هذا الحساب مملوكا لشركتك"
#. module: base
#: model:res.country,name:base.za
@ -5171,7 +5192,7 @@ msgstr "حقل"
#. module: base
#: model:ir.module.module,shortdesc:base.module_project_long_term
msgid "Long Term Projects"
msgstr ""
msgstr "مشروعات المدى البعيد"
#. module: base
#: model:res.country,name:base.ve
@ -12982,7 +13003,7 @@ msgstr "الصحراء الغربية"
#. module: base
#: model:ir.module.category,name:base.module_category_account_voucher
msgid "Invoicing & Payments"
msgstr ""
msgstr "الفواتير و المدفوعات"
#. module: base
#: model:ir.actions.act_window,help:base.action_res_company_form
@ -13186,7 +13207,7 @@ msgstr ""
#. module: base
#: field:res.partner.bank,bank_name:0
msgid "Bank Name"
msgstr ""
msgstr "اسم البنك"
#. module: base
#: model:res.country,name:base.ki
@ -13232,6 +13253,8 @@ msgid ""
"are available. To add a new language, you can use the 'Load an Official "
"Translation' wizard available from the 'Administration' menu."
msgstr ""
"اللغة الافتراضية المستخدمة في الواجهات، عندما تكون هناك ترجمات متوفرة. "
"لإضافة لغة جديدة، يمكنك استخدام 'تحميل ترجمة رسمية' من قائمة \"إدارة\"."
#. module: base
#: model:ir.module.module,description:base.module_l10n_es
@ -13265,7 +13288,7 @@ msgstr "ملف CSV"
#: code:addons/base/res/res_company.py:154
#, python-format
msgid "Phone: "
msgstr ""
msgstr "هاتف "
#. module: base
#: field:res.company,account_no:0
@ -13304,7 +13327,7 @@ msgstr ""
#. module: base
#: field:res.company,vat:0
msgid "Tax ID"
msgstr ""
msgstr "رقم الضرائب"
#. module: base
#: field:ir.model.fields,field_description:0
@ -13426,7 +13449,7 @@ msgstr "الأنشطة"
#. module: base
#: model:ir.module.module,shortdesc:base.module_product
msgid "Products & Pricelists"
msgstr ""
msgstr "المنتجات و قوائم الاسعار"
#. module: base
#: field:ir.actions.act_window,auto_refresh:0
@ -13477,7 +13500,7 @@ msgstr ""
#. module: base
#: field:ir.model.data,name:0
msgid "External Identifier"
msgstr ""
msgstr "مُعرف خارجي"
#. module: base
#: model:ir.actions.act_window,name:base.grant_menu_access
@ -13542,7 +13565,7 @@ msgstr "تصدير"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_nl
msgid "Netherlands - Accounting"
msgstr ""
msgstr "هولندا - محاسبة"
#. module: base
#: field:res.bank,bic:0
@ -13651,7 +13674,7 @@ msgstr "الدليل التقني"
#. module: base
#: view:res.company:0
msgid "Address Information"
msgstr ""
msgstr "معلومات العنوان"
#. module: base
#: model:res.country,name:base.tz
@ -13676,7 +13699,7 @@ msgstr "جزيرة الكريسماس"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_livechat
msgid "Live Chat Support"
msgstr ""
msgstr "التحدث مع الدعم الفني"
#. module: base
#: view:ir.actions.server:0
@ -13876,7 +13899,7 @@ msgstr ""
#. module: base
#: help:ir.actions.act_window,usage:0
msgid "Used to filter menu and home actions from the user form."
msgstr ""
msgstr "تستخدم لتصفية القائمة و الإجراءات الرئيسية من النموذج المستخدم."
#. module: base
#: model:res.country,name:base.sa
@ -13886,7 +13909,7 @@ msgstr "المملكة العربية السعودية"
#. module: base
#: help:res.company,rml_header1:0
msgid "Appears by default on the top right corner of your printed documents."
msgstr ""
msgstr "يظهر إفتراضيا في أعلى الزاوية اليمنى من الوثائق المطبوعة."
#. module: base
#: model:ir.module.module,shortdesc:base.module_fetchmail_crm_claim
@ -13989,7 +14012,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,description:base.module_auth_openid
msgid "Allow users to login through OpenID."
msgstr ""
msgstr "السماح للمستخدمين بالدخول باستخدام أوبن أي دي(OpenID)."
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_payment
@ -14036,7 +14059,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_report_designer
msgid "Report Designer"
msgstr ""
msgstr "مصمم التقارير"
#. module: base
#: model:ir.ui.menu,name:base.menu_address_book
@ -14075,7 +14098,7 @@ msgstr "الفرص والفرص المحتملة"
#. module: base
#: selection:base.language.install,lang:0
msgid "Romanian / română"
msgstr ""
msgstr "الرومانية / română"
#. module: base
#: view:res.log:0
@ -14129,7 +14152,7 @@ msgstr "تنفيذ للإنشاء"
#. module: base
#: model:res.country,name:base.vi
msgid "Virgin Islands (USA)"
msgstr ""
msgstr "جزيرة فيرجين - (الولايات المتحدة اﻻمريكية)"
#. module: base
#: model:res.country,name:base.tw
@ -14169,12 +14192,12 @@ msgstr ""
#. module: base
#: field:ir.ui.view,field_parent:0
msgid "Child Field"
msgstr ""
msgstr "حقل فرعي."
#. module: base
#: view:ir.rule:0
msgid "Detailed algorithm:"
msgstr ""
msgstr "تفاصيل الخوارزمية."
#. module: base
#: field:ir.actions.act_window,usage:0
@ -14195,7 +14218,7 @@ msgstr "workflow.workitem"
#. module: base
#: model:ir.module.module,shortdesc:base.module_profile_tools
msgid "Miscellaneous Tools"
msgstr ""
msgstr "أدوات متنوعة."
#. module: base
#: model:ir.module.category,description:base.module_category_tools
@ -14242,7 +14265,7 @@ msgstr "عرض:"
#. module: base
#: field:ir.model.fields,view_load:0
msgid "View Auto-Load"
msgstr ""
msgstr "عرض التحميل التلقائي"
#. module: base
#: code:addons/base/ir/ir_model.py:264
@ -14268,7 +14291,7 @@ msgstr ""
#. module: base
#: field:ir.ui.menu,web_icon:0
msgid "Web Icon File"
msgstr ""
msgstr "ملف ايقونة الويب"
#. module: base
#: view:base.module.upgrade:0
@ -14289,7 +14312,7 @@ msgstr "Persian / فارسي"
#. module: base
#: view:ir.actions.act_window:0
msgid "View Ordering"
msgstr ""
msgstr "عرض الطلبات"
#. module: base
#: code:addons/base/module/wizard/base_module_upgrade.py:95
@ -14375,7 +14398,7 @@ msgstr "جزيرة أروبا"
#: code:addons/base/module/wizard/base_module_import.py:60
#, python-format
msgid "File is not a zip file!"
msgstr ""
msgstr "الملف ليس ملف مضغوط(zip)!!"
#. module: base
#: model:res.country,name:base.ar
@ -14504,7 +14527,7 @@ msgstr "عقد ضمان الناشر"
#. module: base
#: selection:base.language.install,lang:0
msgid "Bulgarian / български език"
msgstr ""
msgstr "البلغارية / български език"
#. module: base
#: model:ir.ui.menu,name:base.menu_aftersale
@ -14514,7 +14537,7 @@ msgstr "خدمات بعد البيع"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_fr
msgid "France - Accounting"
msgstr ""
msgstr "فرنسا - محاسبة"
#. module: base
#: view:ir.actions.todo:0
@ -14651,7 +14674,7 @@ msgstr ""
#. module: base
#: selection:base.language.install,lang:0
msgid "Czech / Čeština"
msgstr ""
msgstr "التشيكية / Čeština"
#. module: base
#: model:ir.module.category,name:base.module_category_generic_modules
@ -14778,7 +14801,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_plugin_thunderbird
msgid "Thunderbird Plug-In"
msgstr ""
msgstr "اضافات - ثندربيرد"
#. module: base
#: model:ir.model,name:base.model_res_country
@ -14796,7 +14819,7 @@ msgstr "الدولة"
#. module: base
#: model:ir.module.module,shortdesc:base.module_project_messages
msgid "In-Project Messaging System"
msgstr ""
msgstr "نظام المراسلة في المشاريع"
#. module: base
#: model:res.country,name:base.pn
@ -14827,7 +14850,7 @@ msgstr ""
#: view:res.partner:0
#: view:res.partner.address:0
msgid "Change Color"
msgstr ""
msgstr "تغيير اللون"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_15
@ -14861,7 +14884,7 @@ msgstr ""
#. module: base
#: field:ir.module.module,auto_install:0
msgid "Automatic Installation"
msgstr ""
msgstr "تحميل تلقائي"
#. module: base
#: model:res.country,name:base.jp
@ -14930,7 +14953,7 @@ msgstr "ir.actions.server"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_ca
msgid "Canada - Accounting"
msgstr ""
msgstr "كندا - محاسبة"
#. module: base
#: model:ir.actions.act_window,name:base.act_ir_actions_todo_form

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -510,7 +510,7 @@ class actions_server(osv.osv):
'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
"It is a Python block that can use the same values as for the condition field"),
'sequence': fields.integer('Sequence', help="Important when you deal with multiple actions, the execution order will be decided based on this, low number is higher priority."),
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create)."),
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),

View File

@ -22,6 +22,7 @@
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from email.Charset import Charset
from email.Header import Header
from email.Utils import formatdate, make_msgid, COMMASPACE
from email import Encoders
@ -97,6 +98,26 @@ def encode_header(header_text):
return header_text_ascii if header_text_ascii\
else Header(header_text_utf8, 'utf-8')
def encode_header_param(param_text):
"""Returns an appropriate RFC2047 encoded representation of the given
header parameter value, suitable for direct assignation as the
param value (e.g. via Message.set_param() or Message.add_header())
RFC2822 assumes that headers contain only 7-bit characters,
so we ensure it is the case, using RFC2047 encoding when needed.
:param param_text: unicode or utf-8 encoded string with header value
:rtype: string
:return: if ``param_text`` represents a plain ASCII string,
return the same 7-bit string, otherwise returns an
ASCII string containing the RFC2047 encoded text.
"""
# For details see the encode_header() method that uses the same logic
if not param_text: return ""
param_text_utf8 = tools.ustr(param_text).encode('utf-8')
param_text_ascii = try_coerce_ascii(param_text_utf8)
return param_text_ascii if param_text_ascii\
else Charset('utf8').header_encode(param_text_utf8)
name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>')
address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)')
@ -309,7 +330,7 @@ class ir_mail_server(osv.osv):
msg['Cc'] = encode_rfc2822_address_header(COMMASPACE.join(email_cc))
if email_bcc:
msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc))
msg['Date'] = formatdate(localtime=True)
msg['Date'] = formatdate()
# Custom headers may override normal headers or provide additional ones
for key, value in headers.iteritems():
msg[ustr(key).encode('utf-8')] = encode_header(value)
@ -334,14 +355,16 @@ class ir_mail_server(osv.osv):
if attachments:
for (fname, fcontent) in attachments:
filename_utf8 = ustr(fname).encode('utf-8')
filename_rfc2047 = encode_header_param(fname)
part = MIMEBase('application', "octet-stream")
# The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
# so we fix it by using RFC2047 encoding for the filename instead.
part.set_param('name', filename_rfc2047)
part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)
part.set_payload(fcontent)
Encoders.encode_base64(part)
# Force RFC2231 encoding for attachment filename
# See email.message.Message.add_header doc
part.add_header('Content-Disposition', 'attachment',
filename=('utf-8',None,filename_utf8))
msg.attach(part)
return msg

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 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
@ -23,20 +24,21 @@ import re
import time
import types
from osv import fields,osv
import netsvc
from osv.orm import except_orm, browse_record
import tools
from tools.safe_eval import safe_eval as eval
from tools import config
from tools.translate import _
import pooler
from openerp.osv import fields,osv
from openerp import netsvc, pooler, tools
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import config
from openerp.tools.translate import _
from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \
EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT
_logger = logging.getLogger(__name__)
MODULE_UNINSTALL_FLAG = '_force_unlink'
def _get_fields_type(self, cr, uid, context=None):
# Avoid too many nested `if`s below, as RedHat's Python 2.6
# break on it. See bug 939653.
# break on it. See bug 939653.
return sorted([(k,k) for k,v in fields.__dict__.iteritems()
if type(v) == types.TypeType and \
issubclass(v, fields._column) and \
@ -73,7 +75,7 @@ class ir_model(osv.osv):
def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
if not domain:
return []
field, operator, value = domain[0]
__, operator, value = domain[0]
if operator not in ['=', '!=']:
raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
value = bool(value) if operator == '=' else not bool(value)
@ -136,13 +138,33 @@ class ir_model(osv.osv):
super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
context=context)
def _drop_table(self, cr, uid, ids, context=None):
for model in self.browse(cr, uid, ids, context):
model_pool = self.pool.get(model.model)
cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
result = cr.fetchone()
if result and result[0] == 'v':
cr.execute('DROP view %s' % (model_pool._table,))
elif result and result[0] == 'r':
cr.execute('DROP TABLE %s' % (model_pool._table,))
return True
def unlink(self, cr, user, ids, context=None):
for model in self.browse(cr, user, ids, context):
if model.state != 'manual':
raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
# Prevent manual deletion of module tables
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
self._drop_table(cr, user, ids, context)
res = super(ir_model, self).unlink(cr, user, ids, context)
pooler.restart_pool(cr.dbname)
if not context.get(MODULE_UNINSTALL_FLAG):
# only reload pool for normal unlink. For module uninstall the
# reload is done independently in openerp.modules.loading
pooler.restart_pool(cr.dbname)
return res
def write(self, cr, user, ids, vals, context=None):
@ -266,16 +288,30 @@ class ir_model_fields(osv.osv):
('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
]
def _drop_column(self, cr, uid, ids, context=None):
for field in self.browse(cr, uid, ids, context):
model = self.pool.get(field.model)
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
result = cr.fetchone()
cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
column_name = cr.fetchone()
if column_name and (result and result[0] == 'r'):
cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
model._columns.pop(field.name, None)
return True
def unlink(self, cr, user, ids, context=None):
for field in self.browse(cr, user, ids, context):
if field.state <> 'manual':
raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,))
#
# MAY BE ADD A ALTER TABLE DROP ?
#
#Removing _columns entry for that table
self.pool.get(field.model)._columns.pop(field.name,None)
return super(ir_model_fields, self).unlink(cr, user, ids, context)
# Prevent manual deletion of module columns
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
self._drop_column(cr, user, ids, context)
res = super(ir_model_fields, self).unlink(cr, user, ids, context)
return res
def create(self, cr, user, vals, context=None):
if 'model_id' in vals:
@ -295,7 +331,7 @@ class ir_model_fields(osv.osv):
raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
if self.pool.get(vals['model']):
self.pool.get(vals['model']).__init__(self.pool, cr)
@ -414,7 +450,7 @@ class ir_model_fields(osv.osv):
ctx = context.copy()
ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
for model_key, patch_struct in models_patch.items():
for __, patch_struct in models_patch.items():
obj = patch_struct[0]
for col_name, col_prop, val in patch_struct[1]:
setattr(obj._columns[col_name], col_prop, val)
@ -622,10 +658,10 @@ class ir_model_data(osv.osv):
def __init__(self, pool, cr):
osv.osv.__init__(self, pool, cr)
self.doinit = True
# also stored in pool to avoid being discarded along with this osv instance
if getattr(pool, 'model_data_reference_ids', None) is None:
self.pool.model_data_reference_ids = {}
self.loads = self.pool.model_data_reference_ids
def _auto_init(self, cr, context=None):
@ -670,6 +706,7 @@ class ir_model_data(osv.osv):
id = False
return id
def unlink(self, cr, uid, ids, context=None):
""" Regular unlink method, but make sure to clear the caches. """
self._get_id.clear_cache(self)
@ -680,17 +717,14 @@ class ir_model_data(osv.osv):
model_obj = self.pool.get(model)
if not context:
context = {}
# records created during module install should result in res.log entries that are already read!
context = dict(context, res_log_read=True)
if xml_id and ('.' in xml_id):
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
module, xml_id = xml_id.split('.')
if (not xml_id) and (not self.doinit):
return False
action_id = False
if xml_id:
cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
@ -794,52 +828,139 @@ class ir_model_data(osv.osv):
cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
return True
def _module_data_uninstall(self, cr, uid, ids, context=None):
"""Deletes all the records referenced by the ir.model.data entries
``ids`` along with their corresponding database backed (including
dropping tables, columns, FKs, etc, as long as there is no other
ir.model.data entry holding a reference to them (which indicates that
they are still owned by another module).
Attempts to perform the deletion in an appropriate order to maximize
the chance of gracefully deleting all records.
This step is performed as part of the full uninstallation of a module.
"""
if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
context = dict(context or {})
context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
ids_set = set(ids)
wkf_todo = []
to_unlink = []
to_drop_table = []
ids.sort()
ids.reverse()
for data in self.browse(cr, uid, ids, context):
model = data.model
res_id = data.res_id
model_obj = self.pool.get(model)
name = tools.ustr(data.name)
if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\
or name.startswith(EXT_ID_PREFIX_CONSTRAINT):
# double-check we are really going to delete all the owners of this schema element
cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,))
external_ids = [x[0] for x in cr.fetchall()]
if (set(external_ids)-ids_set):
# as installed modules have defined this element we must not delete it!
continue
if name.startswith(EXT_ID_PREFIX_FK):
name = name[len(EXT_ID_PREFIX_FK):]
# test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
if cr.fetchone():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
_logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
continue
if name.startswith(EXT_ID_PREFIX_M2M_TABLE):
name = name[len(EXT_ID_PREFIX_M2M_TABLE):]
cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
if cr.fetchone() and not name in to_drop_table:
to_drop_table.append(name)
continue
if name.startswith(EXT_ID_PREFIX_CONSTRAINT):
name = name[len(EXT_ID_PREFIX_CONSTRAINT):]
# test if constraint exists
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
if cr.fetchone():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
_logger.info('Dropped CONSTRAINT %s@%s', name, model)
continue
pair_to_unlink = (model, res_id)
if pair_to_unlink not in to_unlink:
to_unlink.append(pair_to_unlink)
if model == 'workflow.activity':
# Special treatment for workflow activities: temporarily revert their
# incoming transition and trigger an update to force all workflow items
# to move out before deleting them
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
wkf_todo.extend(cr.fetchall())
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
wf_service = netsvc.LocalService("workflow")
for model,res_id in wkf_todo:
try:
wf_service.trg_write(uid, model, res_id, cr)
except:
_logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
# drop m2m relation tables
for table in to_drop_table:
cr.execute('DROP TABLE %s CASCADE'% (table),)
_logger.info('Dropped table %s', table)
def unlink_if_refcount(to_unlink):
for model, res_id in to_unlink:
external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if (set(external_ids)-ids_set):
# if other modules have defined this record, we must not delete it
return
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
except:
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
# Remove non-model records first, then model fields, and finish with models
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
if model not in ('ir.model','ir.model.fields'))
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
if model == 'ir.model.fields')
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
if model == 'ir.model')
cr.commit()
def _process_end(self, cr, uid, modules):
""" Clear records removed from updated module data.
This method is called at the end of the module loading process.
It is meant to removed records that are no longer present in the
updated data. Such records are recognised as the one with an xml id
and a module in ir_model_data and noupdate set to false, but not
present in self.loads.
"""
if not modules:
return True
modules = list(modules)
module_in = ",".join(["%s"] * len(modules))
cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
wkf_todo = []
to_unlink = []
for (id, name, model, res_id,module) in cr.fetchall():
cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""",
(tuple(modules), False))
for (id, name, model, res_id, module) in cr.fetchall():
if (module,name) not in self.loads:
to_unlink.append((model,res_id))
if model=='workflow.activity':
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
wkf_todo.extend(cr.fetchall())
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
for model,id in wkf_todo:
wf_service = netsvc.LocalService("workflow")
wf_service.trg_write(uid, model, id, cr)
cr.commit()
if not config.get('import_partial'):
for (model, res_id) in to_unlink:
if self.pool.get(model):
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, [res_id])
cr.commit()
except Exception:
cr.rollback()
_logger.warning(
'Could not delete obsolete record with id: %d of model %s\n'
'There should be some relation that points to this resource\n'
'You should manually fix this and restart with --update=module',
res_id, model)
return True
ir_model_data()
self.pool.get(model).unlink(cr, uid, [res_id])
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -419,7 +419,7 @@ class ir_values(osv.osv):
results[action['name']] = (action['id'], action['name'], action_def)
except except_orm, e:
continue
return results.values()
return sorted(results.values())
def _map_legacy_model_list(self, model_list, map_fn, merge_results=False):
"""Apply map_fn to the various models passed, according to

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 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
@ -15,19 +15,18 @@
# 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/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv
from tools import graph
import netsvc
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp import netsvc
class workflow(osv.osv):
_name = "workflow"
_table = "wkf"
_order = "name"
# _log_access = False
_columns = {
'name': fields.char('Name', size=64, required=True),
'osv': fields.char('Resource Object', size=64, required=True,select=True),
@ -46,78 +45,33 @@ class workflow(osv.osv):
return super(workflow, self).write(cr, user, ids, vals, context=context)
def get_active_workitems(self, cr, uid, res, res_id, context=None):
cr.execute('select * from wkf where osv=%s limit 1',(res,))
wkfinfo = cr.dictfetchone()
workitems = []
if wkfinfo:
cr.execute('SELECT id FROM wkf_instance \
WHERE res_id=%s AND wkf_id=%s \
ORDER BY state LIMIT 1',
(res_id, wkfinfo['id']))
inst_id = cr.fetchone()
cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (inst_id,))
workitems = dict(cr.fetchall())
workitems = dict(cr.fetchall())
return {'wkf': wkfinfo, 'workitems': workitems}
#
# scale = (vertical-distance, horizontal-distance, min-node-width(optional), min-node-height(optional), margin(default=20))
#
# def graph_get(self, cr, uid, id, scale, context={}):
#
# nodes= []
# nodes_name = []
# transitions = []
# start = []
# tres = {}
# no_ancester = []
# workflow = self.browse(cr, uid, id, context)
# for a in workflow.activities:
# nodes_name.append((a.id,a.name))
# nodes.append(a.id)
# if a.flow_start:
# start.append(a.id)
# else:
# if not a.in_transitions:
# no_ancester.append(a.id)
#
# for t in a.out_transitions:
# transitions.append((a.id, t.act_to.id))
# tres[t.id] = (a.id, t.act_to.id)
#
#
# g = graph(nodes, transitions, no_ancester)
# g.process(start)
# g.scale(*scale)
# result = g.result_get()
# results = {}
#
# for node in nodes_name:
# results[str(node[0])] = result[node[0]]
# results[str(node[0])]['name'] = node[1]
#
# return {'nodes': results, 'transitions': tres}
def create(self, cr, user, vals, context=None):
if not context:
context={}
wf_service = netsvc.LocalService("workflow")
wf_service.clear_cache(cr, user)
return super(workflow, self).create(cr, user, vals, context=context)
workflow()
class wkf_activity(osv.osv):
_name = "workflow.activity"
_table = "wkf_activity"
_order = "name"
# _log_access = False
_columns = {
'name': fields.char('Name', size=64, required=True),
'wkf_id': fields.many2one('workflow', 'Workflow', required=True, select=True, ondelete='cascade'),
@ -138,22 +92,29 @@ class wkf_activity(osv.osv):
'join_mode': lambda *a: 'XOR',
'split_mode': lambda *a: 'XOR',
}
def unlink(self, cr, uid, ids, context=None):
if context is None: context = {}
if not context.get('_force_unlink') and self.pool.get('workflow.workitem').search(cr, uid, [('act_id', 'in', ids)]):
raise osv.except_osv(_('Operation forbidden'),
_('Please make sure no workitems refer to an activity before deleting it!'))
super(wkf_activity, self).unlink(cr, uid, ids, context=context)
wkf_activity()
class wkf_transition(osv.osv):
_table = "wkf_transition"
_name = "workflow.transition"
# _log_access = False
_rec_name = 'signal'
_columns = {
'trigger_model': fields.char('Trigger Object', size=128),
'trigger_expr_id': fields.char('Trigger Expression', size=128),
'signal': fields.char('Signal (button Name)', size=64,
'signal': fields.char('Signal (button Name)', size=64,
help="When the operation of transition comes from a button pressed in the client form, "\
"signal tests the name of the pressed button. If signal is NULL, no button is necessary to validate this transition."),
'group_id': fields.many2one('res.groups', 'Group Required',
'group_id': fields.many2one('res.groups', 'Group Required',
help="The group that a user must have to be authorized to validate this transition."),
'condition': fields.char('Condition', required=True, size=128,
'condition': fields.char('Condition', required=True, size=128,
help="Expression to be satisfied if we want the transition done."),
'act_from': fields.many2one('workflow.activity', 'Source Activity', required=True, select=True, ondelete='cascade',
help="Source activity. When this activity is over, the condition is tested to determine if we can start the ACT_TO activity."),
@ -194,7 +155,7 @@ class wkf_workitem(osv.osv):
_log_access = False
_rec_name = 'state'
_columns = {
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="restrict", select=True),
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),

View File

@ -2,8 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
# Copyright (C) 2004-2012 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
@ -19,26 +18,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
import cStringIO
import imp
import logging
import os
import re
import StringIO
import urllib
import zipfile
import zipimport
import openerp.modules as addons
import pooler
import release
import tools
from tools.parse_version import parse_version
from tools.translate import _
from osv import fields, osv, orm
from openerp import modules, pooler, release, tools
from openerp.tools.parse_version import parse_version
from openerp.tools.translate import _
from openerp.osv import fields, osv, orm
_logger = logging.getLogger(__name__)
@ -96,7 +85,7 @@ class module(osv.osv):
def get_module_info(cls, name):
info = {}
try:
info = addons.load_information_from_description_file(name)
info = modules.load_information_from_description_file(name)
info['version'] = release.major_version + '.' + info['version']
except Exception:
_logger.debug('Error when trying to fetch informations for '
@ -124,7 +113,7 @@ class module(osv.osv):
if field_name is None or 'menus_by_module' in field_name:
dmodels.append('ir.ui.menu')
assert dmodels, "no models for %s" % field_name
for module_rec in self.browse(cr, uid, ids, context=context):
res[module_rec.id] = {
'menus_by_module': [],
@ -166,7 +155,7 @@ class module(osv.osv):
except Exception, e:
_logger.warning('Unknown error while fetching data of %s',
module_rec.name, exc_info=True)
for key, value in res.iteritems():
for key, _ in res.iteritems():
for k, v in res[key].iteritems():
res[key][k] = "\n".join(sorted(v))
return res
@ -185,7 +174,7 @@ class module(osv.osv):
# installed_version refer the latest version (the one on disk)
# latest_version refer the installed version (the one in database)
# published_version refer the version available on the repository
'installed_version': fields.function(_get_latest_version,
'installed_version': fields.function(_get_latest_version,
string='Latest version', type='char'),
'latest_version': fields.char('Installed version', size=64, readonly=True),
'published_version': fields.char('Published Version', size=64, readonly=True),
@ -277,7 +266,7 @@ class module(osv.osv):
while parts:
part = parts.pop()
try:
f, path, descr = imp.find_module(part, path and [path] or None)
_, path, _ = imp.find_module(part, path and [path] or None)
except ImportError:
raise ImportError('No module named %s' % (pydep,))
@ -344,7 +333,6 @@ class module(osv.osv):
# Mark them to be installed.
if to_install_ids:
self.button_install(cr, uid, to_install_ids, context=context)
return dict(ACTION_DICT, name=_('Install'))
def button_immediate_install(self, cr, uid, ids, context=None):
@ -357,7 +345,7 @@ class module(osv.osv):
"""
self.button_install(cr, uid, ids, context=context)
cr.commit()
db, pool = pooler.restart_pool(cr.dbname, update_module=True)
_, 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'):
@ -377,20 +365,49 @@ class module(osv.osv):
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
return True
def module_uninstall(self, cr, uid, ids, context=None):
"""Perform the various steps required to uninstall a module completely
including the deletion of all database structures created by the module:
tables, columns, constraints, etc."""
ir_model_data = self.pool.get('ir.model.data')
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)])
ir_model_data._module_data_uninstall(cr, uid, data_ids, context)
ir_model_data.unlink(cr, uid, data_ids, context)
self.write(cr, uid, ids, {'state': 'uninstalled'})
return True
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
exclude_states=['uninstalled','uninstallable','to remove'],
context=None):
"""Return the ids of all modules that directly or indirectly depend
on the given module `ids`, and that satisfy the `exclude_states`
filter"""
if not ids: return []
known_dep_ids = set(known_dep_ids or [])
cr.execute('''SELECT DISTINCT m.id
FROM
ir_module_module_dependency d
JOIN
ir_module_module m ON (d.module_id=m.id)
WHERE
d.name IN (SELECT name from ir_module_module where id in %s) AND
m.state NOT IN %s AND
m.id NOT IN %s ''',
(tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids)))
new_dep_ids = set([m[0] for m in cr.fetchall()])
missing_mod_ids = new_dep_ids - known_dep_ids
known_dep_ids |= new_dep_ids
if missing_mod_ids:
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
known_dep_ids, exclude_states,context))
return list(known_dep_ids)
def button_uninstall(self, cr, uid, ids, context=None):
for module in self.browse(cr, uid, ids):
cr.execute('''select m.state,m.name
from
ir_module_module_dependency d
join
ir_module_module m on (d.module_id=m.id)
where
d.name=%s and
m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,))
res = cr.fetchall()
if res:
raise orm.except_orm(_('Error'), _('Some installed modules depend on the module you plan to Uninstall :\n %s') % '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res)))
self.write(cr, uid, ids, {'state': 'to remove'})
if any(m.name == 'base' for m in self.browse(cr, uid, ids)):
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'})
return dict(ACTION_DICT, name=_('Uninstall'))
def button_uninstall_cancel(self, cr, uid, ids, context=None):
@ -453,6 +470,7 @@ class module(osv.osv):
'sequence': terp.get('sequence', 100),
'application': terp.get('application', False),
'auto_install': terp.get('auto_install', False),
'icon': terp.get('icon', False),
}
# update the list of available packages
@ -463,7 +481,7 @@ class module(osv.osv):
known_mods_names = dict([(m.name, m) for m in known_mods])
# iterate through detected modules and update/create them in db
for mod_name in addons.get_modules():
for mod_name in modules.get_modules():
mod = known_mods_names.get(mod_name)
terp = self.get_module_info(mod_name)
values = self.get_values_from_terp(terp)
@ -472,7 +490,7 @@ class module(osv.osv):
updated_values = {}
for key in values:
old = getattr(mod, key)
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
if not old == updated:
updated_values[key] = values[key]
if terp.get('installable', True) and mod.state == 'uninstallable':
@ -482,7 +500,7 @@ class module(osv.osv):
if updated_values:
self.write(cr, uid, mod.id, updated_values)
else:
mod_path = addons.get_module_path(mod_name)
mod_path = modules.get_module_path(mod_name)
if not mod_path:
continue
if not terp or not terp.get('installable', True):
@ -511,7 +529,7 @@ class module(osv.osv):
if not download:
continue
zip_content = urllib.urlopen(mod.url).read()
fname = addons.get_module_path(str(mod.name)+'.zip', downloaded=True)
fname = modules.get_module_path(str(mod.name)+'.zip', downloaded=True)
try:
with open(fname, 'wb') as fp:
fp.write(zip_content)
@ -581,17 +599,17 @@ class module(osv.osv):
for mod in self.browse(cr, uid, ids):
if mod.state != 'installed':
continue
modpath = addons.get_module_path(mod.name)
modpath = modules.get_module_path(mod.name)
if not modpath:
# unable to find the module. we skip
continue
for lang in filter_lang:
iso_lang = tools.get_iso_codes(lang)
f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
context2 = context and context.copy() or {}
if f and '_' in iso_lang:
iso_lang2 = iso_lang.split('_')[0]
f2 = addons.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
f2 = modules.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
if f2:
_logger.info('module %s: loading base translation file %s for language %s', mod.name, iso_lang2, lang)
tools.trans_load(cr, f2, lang, verbose=False, context=context)
@ -601,7 +619,7 @@ class module(osv.osv):
# like "en".
if (not f) and '_' in iso_lang:
iso_lang = iso_lang.split('_')[0]
f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
if f:
_logger.info('module %s: loading translation file (%s) for language %s', mod.name, iso_lang, lang)
tools.trans_load(cr, f, lang, verbose=False, context=context2)
@ -660,8 +678,12 @@ class module_dependency(osv.osv):
return result
_columns = {
# The dependency name
'name': fields.char('Name', size=128, select=True),
# The module that depends on it
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
'state': fields.function(_state, type='selection', selection=[
('uninstallable','Uninstallable'),
('uninstalled','Not Installed'),

View File

@ -153,7 +153,9 @@
<group col="6" colspan="2">
<button name="button_install" states="uninstalled" string="Install" icon="terp-gtk-jump-to-ltr" type="object"/>
<button name="button_install_cancel" states="to install" string="Cancel Install" icon="gtk-cancel" type="object"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)" icon="terp-dialog-close" type="object"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)"
icon="terp-dialog-close" 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" icon="gtk-cancel" type="object"/>
<button name="button_upgrade" states="installed" string="Upgrade" icon="terp-gtk-go-back-rtl" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" icon="gtk-cancel" type="object"/>

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 OpenERP SA (<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
@ -19,9 +19,9 @@
#
##############################################################################
import pooler
from osv import osv, fields
from tools.translate import _
from openerp import pooler
from openerp.osv import osv, fields
from openerp.tools.translate import _
class base_module_upgrade(osv.osv_memory):
""" Module Upgrade """
@ -34,13 +34,6 @@ class base_module_upgrade(osv.osv_memory):
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
""" Changes the view dynamically
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param context: A standard dictionary
@return: New arch of view.
"""
res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
if view_type != 'form':
return res
@ -71,45 +64,39 @@ class base_module_upgrade(osv.osv_memory):
return ids
def default_get(self, cr, uid, fields, context=None):
"""
This function checks for precondition before wizard executes
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param fields: List of fields for default value
@param context: A standard dictionary for contextual values
"""
mod_obj = self.pool.get('ir.module.module')
ids = self.get_module_list(cr, uid, context=context)
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
def upgrade_module(self, cr, uid, ids, context=None):
mod_obj = self.pool.get('ir.module.module')
ids = mod_obj.search(cr, uid, [('state', 'in', ['to upgrade', 'to remove', 'to install'])])
unmet_packages = []
mod_dep_obj = self.pool.get('ir.module.module.dependency')
for mod in mod_obj.browse(cr, uid, ids):
depends_mod_ids = mod_dep_obj.search(cr, uid, [('module_id', '=', mod.id)])
for dep_mod in mod_dep_obj.browse(cr, uid, depends_mod_ids):
if dep_mod.state in ('unknown','uninstalled'):
unmet_packages.append(dep_mod.name)
if len(unmet_packages):
raise osv.except_osv(_('Unmet dependency !'), _('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
mod_obj.download(cr, uid, ids, context=context)
cr.commit()
_db, pool = pooler.restart_pool(cr.dbname, update_module=True)
ir_module = self.pool.get('ir.module.module')
data_obj = pool.get('ir.model.data')
id2 = data_obj._get_id(cr, uid, 'base', 'view_base_module_upgrade_install')
if id2:
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
# install/upgrade: double-check preconditions
ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])])
if ids:
cr.execute("""SELECT d.name FROM ir_module_module m
JOIN ir_module_module_dependency d ON (m.id = d.module_id)
LEFT JOIN ir_module_module m2 ON (d.name = m2.name)
WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s)""",
(tuple(ids), ('uninstalled',)))
unmet_packages = [x[0] for x in cr.fetchall()]
if unmet_packages:
raise osv.except_osv(_('Unmet dependency !'),
_('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
ir_module.download(cr, uid, ids, context=context)
cr.commit() # save before re-creating cursor below
pooler.restart_pool(cr.dbname, update_module=True)
ir_model_data = self.pool.get('ir.model.data')
__, res_id = ir_model_data.get_object_reference(cr, uid, 'base', 'view_base_module_upgrade_install')
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'base.module.upgrade',
'views': [(id2, 'form')],
'views': [(res_id, 'form')],
'view_id': False,
'type': 'ir.actions.act_window',
'target': 'new',
@ -118,6 +105,5 @@ class base_module_upgrade(osv.osv_memory):
def config(self, cr, uid, ids, context=None):
return self.pool.get('res.config').next(cr, uid, [], context=context)
base_module_upgrade()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -6,15 +6,13 @@
<rml_footer2 type="field" name="rml_footer2"/>
<title type="field" name="partner_id.title"/>
<name type="field" name="partner_id.name"/>
<address type="zoom" name="partner_id.address">
<street type="field" name="street"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
<phone type="field" name="phone"/>
<email type="field" name="email"/>
</address>
<street type="field" name="street"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
<phone type="field" name="phone"/>
<email type="field" name="email"/>
</corporation>
<user>
<name type="field" name="name"/>

View File

@ -22,18 +22,18 @@
<setFont name="Helvetica" size="10"/>
<drawRightString x="20cm" y="28.5cm"><xsl:value-of select="//corporate-header/corporation/rml_header1"/></drawRightString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/address/street"/></drawString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/street"/></drawString>
<drawString x="1cm" y="26.5cm">
<xsl:value-of select="//corporate-header/corporation/address/zip"/>
<xsl:value-of select="//corporate-header/corporation/zip"/>
<xsl:text> </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/city"/>
<xsl:value-of select="//corporate-header/corporation/city"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/country"/>
<xsl:value-of select="//corporate-header/corporation/country"/>
</drawString>
<drawString x="1cm" y="26cm">Phone:</drawString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/address/phone"/></drawRightString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/phone"/></drawRightString>
<drawString x="1cm" y="25.5cm">Mail:</drawString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/address/email"/></drawRightString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/email"/></drawRightString>
<!--page bottom-->
@ -57,18 +57,18 @@
<setFont name="Helvetica" size="10"/>
<drawRightString x="1cm" y="27.5cm"><xsl:value-of select="//corporate-header/corporation/rml_header1"/></drawRightString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/address/street"/></drawString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/street"/></drawString>
<drawString x="1cm" y="26.5cm">
<xsl:value-of select="//corporate-header/corporation/address/zip"/>
<xsl:value-of select="//corporate-header/corporation/zip"/>
<xsl:text> </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/city"/>
<xsl:value-of select="//corporate-header/corporation/city"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/country"/>
<xsl:value-of select="//corporate-header/corporation/country"/>
</drawString>
<drawString x="1cm" y="26cm">Phone:</drawString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/address/phone"/></drawRightString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/phone"/></drawRightString>
<drawString x="1cm" y="25.5cm">Mail:</drawString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/address/email"/></drawRightString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/email"/></drawRightString>
<!--page bottom-->

View File

@ -204,8 +204,8 @@
</table:table-cell>
</table:table-row>
</table:table>
<text:p text:style-name="P3">[[ company.partner_id.address and company.partner_id.address[0].street ]]</text:p>
<text:p text:style-name="P3">[[ company.partner_id.address and company.partner_id.address[0].zip ]] [[ company.partner_id.address and company.partner_id.address[0].city ]] - [[ company.partner_id.address and company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name ]]</text:p>
<text:p text:style-name="P3">[[ company.partner_id.street ]]</text:p>
<text:p text:style-name="P3">[[ company.partner_id.zip ]] [[ company.partner_id.city ]] - [[ company.partner_id.country_id and company.partner_id.country_id.name ]]</text:p>
<table:table table:name="Table3" table:style-name="Table3">
<table:table-column table:style-name="Table3.A"/>
<table:table-column table:style-name="Table3.B"/>
@ -214,7 +214,7 @@
<text:p text:style-name="P4">Phone :</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table3.A1" table:value-type="string">
<text:p text:style-name="P5">[[ company.partner_id.address and company.partner_id.address[0].phone ]]</text:p>
<text:p text:style-name="P5">[[ company.partner_id.phone ]]</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
@ -222,7 +222,7 @@
<text:p text:style-name="P4">Mail :</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table3.A2" table:value-type="string">
<text:p text:style-name="P5">[[ company.partner_id.address and company.partner_id.address[0].email ]]</text:p>
<text:p text:style-name="P5">[[ company.partner_id.email ]]</text:p>
</table:table-cell>
</table:table-row>
</table:table>

View File

@ -14,33 +14,33 @@
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['street']:
<small>${company.partner_id.address[0].street}</small></br>
% if company['partner_id']['street']:
<small>${company.partner_id.street}</small></br>
%endif
</td>
</tr>
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['zip']:
<small>${company.partner_id.address[0].zip}
${company.partner_id.address[0].city}-${company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name}</small></br>
% if company['partner_id']['zip']:
<small>${company.partner_id.zip}
${company.partner_id.city}-${company.partner_id.country_id and company.partner_id.country_id.name}</small></br>
%endif
</td>
</tr>
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['phone']:
<small><b>Phone:</b>${company.partner_id.address and company.partner_id.address[0].phone}</small></br>
% if company['partner_id']['phone']:
<small><b>Phone:</b>${company.partner_id.phone}</small></br>
%endif
</td>
</tr>
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['email']:
<small><b>Mail:</b>${company.partner_id.address and company.partner_id.address[0].email}</small></br></<address>
% if company['partner_id']['email']:
<small><b>Mail:</b>${company.partner_id.email}</small></br></<address>
%endif
</td>
</tr>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -55,7 +55,7 @@ class ir_property(osv.osv):
'fields_id': fields.many2one('ir.model.fields', 'Field', ondelete='cascade', required=True, select=1),
'value_float' : fields.float('Value'),
'value_integer' : fields.integer_big('Value'), # will contain (int, bigint)
'value_integer' : fields.integer('Value'),
'value_text' : fields.text('Value'), # will contain (char, text)
'value_binary' : fields.binary('Value'),
'value_reference': fields.reference('Value', selection=_models_get2, size=128),
@ -65,7 +65,6 @@ class ir_property(osv.osv):
('float', 'Float'),
('boolean', 'Boolean'),
('integer', 'Integer'),
('integer_big', 'Integer Big'),
('text', 'Text'),
('binary', 'Binary'),
('many2one', 'Many2One'),
@ -100,7 +99,6 @@ class ir_property(osv.osv):
'float': 'value_float',
'boolean' : 'value_integer',
'integer': 'value_integer',
'integer_big': 'value_integer',
'text': 'value_text',
'binary': 'value_binary',
'many2one': 'value_reference',
@ -142,7 +140,7 @@ class ir_property(osv.osv):
return record.value_float
elif record.type == 'boolean':
return bool(record.value_integer)
elif record.type in ('integer', 'integer_big'):
elif record.type == 'integer':
return record.value_integer
elif record.type == 'binary':
return record.value_binary

View File

@ -31,7 +31,7 @@
<separator colspan="4" string="Field Information"/>
<field colspan="4" name="fields_id" select="1"/>
<field colspan="4" name="type"/>
<group colspan="4" attrs="{'invisible' : [('type', 'not in', ('integer', 'integer_big', 'boolean'))]}">
<group colspan="4" attrs="{'invisible' : [('type', 'not in', ('integer', 'boolean'))]}">
<field colspan="4" name="value_integer" widget="integer"/>
</group>
<group colspan="4" attrs="{'invisible' : [('type', '!=', 'float')]}">

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -3,16 +3,12 @@
<address type="fields" name="id">
<company-title type="field" name="title.name"/>
<company-name type="field" name="name"/>
<contact type="zoom" name="address">
<type type="field" name="type"/>
<title type="field" name="title.name"/>
<name type="field" name="name"/>
<street type="field" name="street"/>
<street2 type="field" name="street2"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
</contact>
<type type="field" name="type"/>
<street type="field" name="street"/>
<street2 type="field" name="street2"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
</address>
</addresses>

View File

@ -58,31 +58,17 @@
<xsl:template match="address" mode="story">
<para style="nospace"><xsl:value-of select="company-name"/><xsl:text> </xsl:text><xsl:value-of select="company-title"/></para>
<xsl:choose>
<xsl:when test="count(contact[type='default']) >= 1">
<!-- apply the first 'contact' node with the type 'default' -->
<xsl:apply-templates select="contact[type='default'][1]"/>
</xsl:when>
<xsl:when test="count(contact[type='']) >= 1">
<!-- apply the first 'contact' node with an empty type -->
<xsl:apply-templates select="contact[type=''][1]"/>
</xsl:when>
<xsl:otherwise>
<!-- apply the first 'contact' node -->
<xsl:apply-templates select="contact[1]"/>
</xsl:otherwise>
</xsl:choose>
<para style="nospace"><xsl:value-of select="street"/></para>
<para style="nospace"><xsl:value-of select="street2"/></para>
<para style="nospace"><xsl:value-of select="zip"/><xsl:text> </xsl:text><xsl:value-of select="city"/></para>
<para style="nospace"><xsl:value-of select="state"/></para>
<para style="nospace"><xsl:value-of select="country"/></para>
<xsl:if test="position() &lt; last()">
<nextFrame/>
</xsl:if>
</xsl:template>
<xsl:template match="contact">
<para style="nospace"><xsl:value-of select="title"/><xsl:text> </xsl:text><xsl:value-of select="name"/></para>
<para style="nospace"><xsl:value-of select="street"/></para>
<para style="nospace"><xsl:value-of select="street2"/></para>
<para style="nospace"><xsl:value-of select="zip"/><xsl:text> </xsl:text><xsl:value-of select="city"/></para>
<para style="nospace"><xsl:value-of select="state"/></para>
<para style="nospace"><xsl:value-of select="country"/></para>
</xsl:template>
</xsl:stylesheet>

View File

@ -219,11 +219,11 @@ class res_partner_bank(osv.osv):
if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
result['owner_name'] = part.name
result['street'] = part.address and part.address[0].street or False
result['city'] = part.address and part.address[0].city or False
result['zip'] = part.address and part.address[0].zip or False
result['country_id'] = part.address and part.address[0].country_id and part.address[0].country_id.id or False
result['state_id'] = part.address and part.address[0].state_id and part.address[0].state_id.id or False
result['street'] = part.street or False
result['city'] = part.city or False
result['zip'] = part.zip or False
result['country_id'] = part.country_id.id
result['state_id'] = part.state_id.id
return {'value': result}
res_partner_bank()

View File

@ -77,13 +77,12 @@ class res_company(osv.osv):
""" Read the 'address' functional fields. """
result = {}
part_obj = self.pool.get('res.partner')
address_obj = self.pool.get('res.partner.address')
for company in self.browse(cr, uid, ids, context=context):
result[company.id] = {}.fromkeys(field_names, False)
if company.partner_id:
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['default'])
if address_data['default']:
address = address_obj.read(cr, uid, address_data['default'], field_names, context=context)
address = part_obj.read(cr, uid, address_data['default'], field_names, context=context)
for field in field_names:
result[company.id][field] = address[field] or False
return result
@ -105,13 +104,12 @@ class res_company(osv.osv):
company = self.browse(cr, uid, company_id, context=context)
if company.partner_id:
part_obj = self.pool.get('res.partner')
address_obj = self.pool.get('res.partner.address')
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['default'])
address = address_data['default']
if address:
address_obj.write(cr, uid, [address], {name: value or False})
part_obj.write(cr, uid, [address], {name: value or False})
else:
address_obj.create(cr, uid, {name: value or False, 'partner_id': company.partner_id.id}, context=context)
part_obj.create(cr, uid, {name: value or False, 'parent_id': company.partner_id.id}, context=context)
return True
@ -126,7 +124,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', required=True),
'logo': fields.binary('Logo'),
'logo': fields.related('partner_id', 'photo', 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'),
@ -229,7 +227,7 @@ class res_company(osv.osv):
self.cache_restart(cr)
return super(res_company, self).create(cr, uid, vals, context=context)
obj_partner = self.pool.get('res.partner')
partner_id = obj_partner.create(cr, uid, {'name': vals['name']}, context=context)
partner_id = obj_partner.create(cr, uid, {'name': vals['name'], 'is_company':True}, context=context)
vals.update({'partner_id': partner_id})
self.cache_restart(cr)
company_id = super(res_company, self).create(cr, uid, vals, context=context)
@ -296,12 +294,12 @@ class res_company(osv.osv):
<drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString>
<drawString x="1.3cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].street or '' ]]</drawString>
<drawString x="1.3cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].zip or '' ]] [[ company.partner_id.address and company.partner_id.address[0].city or '' ]] - [[ company.partner_id.address and company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name or '']]</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>
<drawString x="1.3cm" y="%s">Phone:</drawString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].phone or '' ]]</drawRightString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.phone or '' ]]</drawRightString>
<drawString x="1.3cm" y="%s">Mail:</drawString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].email or '' ]]</drawRightString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.email or '' ]]</drawRightString>
<lines>1.3cm %s 7cm %s</lines>
<!--page bottom-->

View File

@ -21,6 +21,23 @@
from osv import fields, osv
def location_name_search(self, cr, user, name='', args=None, operator='ilike',
context=None, limit=100):
if not args:
args = []
ids = []
if len(name) == 2:
ids = self.search(cr, user, [('code', 'ilike', name)] + args,
limit=limit, context=context)
search_domain = [('name', operator, name)]
if ids: search_domain.append(('id', 'not in', ids))
ids.extend(self.search(cr, user, search_domain + args,
limit=limit, context=context))
locations = self.name_get(cr, user, ids, context)
return sorted(locations, key=lambda (id, name): ids.index(id))
class Country(osv.osv):
_name = 'res.country'
@ -46,25 +63,12 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
'The code of the country must be unique !')
]
_defaults = {
'address_format': "%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s\n%(country_name)s",
'address_format': "%(company_name)s\n%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s\n%(country_name)s",
}
def name_search(self, cr, user, name='', args=None, operator='ilike',
context=None, limit=100):
if not args:
args=[]
if not context:
context={}
ids = False
if len(name) == 2:
ids = self.search(cr, user, [('code', 'ilike', name)] + args,
limit=limit, context=context)
if not ids:
ids = self.search(cr, user, [('name', operator, name)] + args,
limit=limit, context=context)
return self.name_get(cr, user, ids, context)
_order='name'
name_search = location_name_search
def create(self, cursor, user, vals, context=None):
if 'code' in vals:
vals['code'] = vals['code'].upper()
@ -77,8 +81,6 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
return super(Country, self).write(cursor, user, ids, vals,
context=context)
Country()
class CountryState(osv.osv):
_description="Country state"
@ -90,23 +92,9 @@ class CountryState(osv.osv):
'code': fields.char('State Code', size=3,
help='The state code in three chars.\n', required=True),
}
def name_search(self, cr, user, name='', args=None, operator='ilike',
context=None, limit=100):
if not args:
args = []
if not context:
context = {}
ids = self.search(cr, user, [('code', 'ilike', name)] + args, limit=limit,
context=context)
if not ids:
ids = self.search(cr, user, [('name', operator, name)] + args,
limit=limit, context=context)
return self.name_get(cr, user, ids, context)
_order = 'code'
CountryState()
name_search = location_name_search
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -41,7 +41,7 @@
<menuitem id="menu_localisation" name="Localisation" parent="menu_config_address_book" sequence="1"/>
<menuitem action="action_country" id="menu_country_partner" parent="menu_localisation" sequence="0"/>
<menuitem action="action_country" id="menu_country_partner" parent="menu_localisation" sequence="0" groups="base.group_no_one"/>
<!--
State
@ -82,7 +82,7 @@
<field name="help">If you are working on the American market, you can manage the different federal states you are working on from here. Each state is attached to one country.</field>
</record>
<menuitem action="action_country_state" id="menu_country_state_partner" parent="menu_localisation" sequence="1"/>
<menuitem action="action_country_state" id="menu_country_state_partner" parent="menu_localisation" sequence="1" groups="base.group_no_one"/>
</data>
</openerp>

View File

@ -19,12 +19,13 @@
#
##############################################################################
import os
import math
from osv import fields,osv
from osv import osv, fields
import tools
import pooler
from tools.translate import _
import logging
import pooler
class res_payterm(osv.osv):
_description = 'Payment term'
@ -33,7 +34,6 @@ class res_payterm(osv.osv):
_columns = {
'name': fields.char('Payment Term (short name)', size=64),
}
res_payterm()
class res_partner_category(osv.osv):
@ -45,7 +45,7 @@ class res_partner_category(osv.osv):
used to select the short version of the
category name (without the direct parent),
when set to ``'short'``. The default is
the long version."""
the long version."""
if context is None:
context = {}
if context.get('partner_category_display') == 'short':
@ -98,7 +98,6 @@ class res_partner_category(osv.osv):
_parent_store = True
_parent_order = 'name'
_order = 'parent_left'
res_partner_category()
class res_partner_title(osv.osv):
_name = 'res.partner.title'
@ -108,33 +107,41 @@ class res_partner_title(osv.osv):
'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24)
}
_order = 'name'
res_partner_title()
def _lang_get(self, cr, uid, context=None):
obj = self.pool.get('res.lang')
ids = obj.search(cr, uid, [], context=context)
res = obj.read(cr, uid, ids, ['code', 'name'], context)
lang_pool = self.pool.get('res.lang')
ids = lang_pool.search(cr, uid, [], context=context)
res = lang_pool.read(cr, uid, ids, ['code', 'name'], context)
return [(r['code'], r['name']) for r in res] + [('','')]
POSTAL_ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'country_id')
ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang')
class res_partner(osv.osv):
_description='Partner'
_name = "res.partner"
def _address_display(self, cr, uid, ids, name, args, context=None):
res={}
for partner in self.browse(cr, uid, ids, context=context):
res[partner.id] =self._display_address(cr, uid, partner, context=context)
return res
_order = "name"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title','Partner Firm'),
'title': fields.many2one('res.partner.title','Title'),
'parent_id': fields.many2one('res.partner','Parent Partner'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Partner Ref.'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'),
'ref': fields.char('Reference', size=64, select=1),
'lang': fields.selection(_lang_get, 'Language', help="If the selected language is loaded in the system, all documents related to this partner will be printed in this language. If not, it will be english."),
'user_id': fields.many2one('res.users', 'Salesman', help='The internal user that is in charge of communicating with this partner if any.'),
'vat': fields.char('VAT',size=32 ,help="Value Added Tax number. Check the box if the partner is subjected to the VAT. Used by the VAT legal statement."),
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
'website': fields.char('Website',size=64, help="Website of Partner."),
'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'),
'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', 'res_partner_category_rel', 'partner_id', 'category_id', 'Categories'),
'events': fields.one2many('res.partner.event', 'partner_id', 'Events'),
'credit_limit': fields.float(string='Credit Limit'),
@ -142,41 +149,84 @@ class res_partner(osv.osv):
'active': fields.boolean('Active'),
'customer': fields.boolean('Customer', help="Check this box if the partner is a customer."),
'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."),
'city': fields.related('address', 'city', type='char', string='City'),
'function': fields.related('address', 'function', type='char', string='function'),
'subname': fields.related('address', 'name', type='char', string='Contact Name'),
'phone': fields.related('address', 'phone', type='char', string='Phone'),
'mobile': fields.related('address', 'mobile', type='char', string='Mobile'),
'country': fields.related('address', 'country_id', type='many2one', relation='res.country', string='Country'),
'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."),
'email': fields.related('address', 'email', type='char', size=240, string='E-mail'),
'function': fields.char('Function', size=128),
'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),
'street2': fields.char('Street2', size=128),
'zip': fields.char('Zip', change_default=True, size=24),
'city': fields.char('City', size=128),
'state_id': fields.many2one("res.country.state", 'Fed. State', domain="[('country_id','=',country_id)]"),
'country_id': fields.many2one('res.country', 'Country'),
'country': fields.related('country_id', type='many2one', relation='res.country', string='Country'), # for backward compatibility
'email': fields.char('E-Mail', size=240),
'phone': fields.char('Phone', size=64),
'fax': fields.char('Fax', size=64),
'mobile': fields.char('Mobile', size=64),
'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'),
'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'),
}
def _default_category(self, cr, uid, context=None):
if context is None:
context = {}
if 'category_id' in context and context['category_id']:
if context.get('category_id'):
return [context['category_id']]
return []
return False
def _get_photo(self, cr, uid, is_company, context=None):
if is_company:
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')
_defaults = {
'active': lambda *a: 1,
'customer': lambda *a: 1,
'active': True,
'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),
'color': 0,
'is_company': False,
'type': 'default',
'use_parent_address': True,
'photo': lambda self, cr, uid, context: self._get_photo(cr, uid, False, context),
}
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': name+ _(' (copy)'), 'events':[]})
default.update({'name': _('%s (copy)')%(name), 'events':[]})
return super(res_partner, self).copy(cr, uid, id, default, context)
def do_share(self, cr, uid, ids, *args):
return True
def onchange_type(self, cr, uid, ids, is_company, context=None):
value = {'title': False,
'photo': self._get_photo(cr, uid, is_company, context)}
if is_company:
value['parent_id'] = False
domain = {'title': [('domain', '=', 'partner')]}
else:
domain = {'title': [('domain', '=', 'contact')]}
return {'value': value, 'domain': domain}
def onchange_address(self, cr, uid, ids, use_parent_address, parent_id, context=None):
def value_or_id(val):
""" return val or val.id if val is a browse record """
return val if isinstance(val, (bool, int, long, float, basestring)) else val.id
if use_parent_address and parent_id:
parent = self.browse(cr, uid, parent_id, context=context)
return {'value': dict((key, value_or_id(parent[key])) for key in ADDRESS_FIELDS)}
return {}
def _check_ean_key(self, cr, uid, ids, context=None):
for partner_o in pooler.get_pool(cr.dbname).get('res.partner').read(cr, uid, ids, ['ean13',]):
@ -196,6 +246,43 @@ class res_partner(osv.osv):
# _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
def write(self, cr, uid, ids, vals, context=None):
# Update parent and siblings or children records
if isinstance(ids, (int, long)):
ids = [ids]
if vals.get('is_company')==False:
vals.update({'child_ids' : [(5,)]})
for partner in self.browse(cr, uid, ids, context=context):
update_ids = []
if partner.is_company:
domain_children = [('parent_id', '=', partner.id), ('use_parent_address', '=', True)]
update_ids = self.search(cr, uid, domain_children, context=context)
elif partner.parent_id:
if vals.get('use_parent_address')==True:
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
if 'use_parent_address' not in vals and partner.use_parent_address:
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
self.update_address(cr, uid, update_ids, vals, context)
return super(res_partner,self).write(cr, uid, ids, vals, context=context)
def create(self, cr, uid, vals, context=None):
if context is None:
context={}
# Update parent and siblings records
if vals.get('parent_id') and vals.get('use_parent_address'):
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)
return super(res_partner,self).create(cr, uid, vals, context=context)
def update_address(self, cr, uid, ids, vals, context=None):
addr_vals = dict((key, vals[key]) for key in POSTAL_ADDRESS_FIELDS if vals.get(key))
return super(res_partner, self).write(cr, uid, ids, addr_vals, context)
def name_get(self, cr, uid, ids, context=None):
if context is None:
context = {}
@ -205,16 +292,28 @@ class res_partner(osv.osv):
rec_name = 'ref'
else:
rec_name = 'name'
res = [(r['id'], r[rec_name]) for r in self.read(cr, uid, ids, [rec_name], context)]
reads = self.read(cr, uid, ids, [rec_name,'parent_id'], context=context)
res = []
for record in reads:
name = record.get('name', '/')
if record['parent_id']:
name = "%s (%s)"%(name, record['parent_id'][1])
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 = []
# short-circuit ref match when possible
if name and operator in ('=', 'ilike', '=ilike', 'like'):
ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context)
# search on the name of the contacts and of its company
name2 = operator == '=' and name or '%' + name + '%'
cr.execute('''SELECT partner.id FROM res_partner partner
LEFT JOIN res_partner company ON partner.parent_id = company.id
WHERE partner.name || ' (' || COALESCE(company.name,'') || ')'
''' + operator + ''' %s ''', (name2,))
ids = map(lambda x: x[0], cr.fetchall())
if args:
ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context)
if ids:
return self.name_get(cr, uid, ids, context)
return super(res_partner,self).name_search(cr, uid, name, args, operator=operator, context=context, limit=limit)
@ -222,9 +321,8 @@ class res_partner(osv.osv):
def _email_send(self, cr, uid, ids, email_from, subject, body, on_error=None):
partners = self.browse(cr, uid, ids)
for partner in partners:
if len(partner.address):
if partner.address[0].email:
tools.email_send(email_from, [partner.address[0].email], subject, body, on_error)
if partner.email:
tools.email_send(email_from, [partner.email], subject, body, on_error)
return True
def email_send(self, cr, uid, ids, email_from, subject, body, on_error=''):
@ -232,7 +330,6 @@ class res_partner(osv.osv):
self.pool.get('ir.cron').create(cr, uid, {
'name': 'Send Partner Emails',
'user_id': uid,
# 'nextcall': False,
'model': 'res.partner',
'function': '_email_send',
'args': repr([ids[:16], email_from, subject, body, on_error])
@ -243,20 +340,22 @@ class res_partner(osv.osv):
def address_get(self, cr, uid, ids, adr_pref=None):
if adr_pref is None:
adr_pref = ['default']
address_obj = self.pool.get('res.partner.address')
address_ids = address_obj.search(cr, uid, [('partner_id', 'in', ids)])
address_rec = address_obj.read(cr, uid, address_ids, ['type'])
res = list((addr['type'],addr['id']) for addr in address_rec)
adr = dict(res)
result = {}
# retrieve addresses from the partner itself and its children
res = []
# need to fix the ids ,It get False value in list like ids[False]
if ids and ids[0]!=False:
for p in self.browse(cr, uid, ids):
res.append((p.type, p.id))
res.extend((c.type, c.id) for c in p.child_ids)
address_dict = dict(reversed(res))
# get the id of the (first) default address if there is one,
# otherwise get the id of the first address in the list
default_address = False
if res:
default_address = adr.get('default', res[0][1])
else:
default_address = False
result = {}
for a in adr_pref:
result[a] = adr.get(a, default_address)
default_address = address_dict.get('default', res[0][1])
for adr in adr_pref:
result[adr] = address_dict.get(adr, default_address)
return result
def gen_next_ref(self, cr, uid, ids):
@ -282,23 +381,55 @@ class res_partner(osv.osv):
if (not context.get('category_id', False)):
return False
return _('Partners: ')+self.pool.get('res.partner.category').browse(cr, uid, context['category_id'], context).name
def main_partner(self, cr, uid):
''' Return the id of the main partner
'''
model_data = self.pool.get('ir.model.data')
return model_data.browse(
cr, uid,
model_data.search(cr, uid, [('module','=','base'),
('name','=','main_partner')])[0],
).res_id
res_partner()
return model_data.browse(cr, uid,
model_data.search(cr, uid, [('module','=','base'),
('name','=','main_partner')])[0],
).res_id
def _display_address(self, cr, uid, address, context=None):
'''
The purpose of this function is to build and return an address formatted accordingly to the
standards of the country where it belongs.
:param address: browse record of the res.partner.address to format
:returns: the address formatted in a display that fit its country habits (or the default ones
if not country is specified)
:rtype: string
'''
# get the information that will be injected into the display format
# get the address format
address_format = address.country_id and address.country_id.address_format or \
'%(company_name)s\n%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s'
args = {
'state_code': address.state_id and address.state_id.code or '',
'state_name': address.state_id and address.state_id.name or '',
'country_code': address.country_id and address.country_id.code or '',
'country_name': address.country_id and address.country_id.name or '',
'company_name': address.parent_id and address.parent_id.name or '',
}
address_field = ['title', 'street', 'street2', 'zip', 'city']
for field in address_field :
args[field] = getattr(address, field) or ''
return address_format % args
# res.partner.address is deprecated; it is still there for backward compability only and will be removed in next version
class res_partner_address(osv.osv):
_description ='Partner Addresses'
_table = "res_partner"
_name = 'res.partner.address'
_order = 'type, name'
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner Name', ondelete='set null', select=True, help="Keep empty for a private address, not related to partner."),
'parent_id': fields.many2one('res.partner', 'Company', ondelete='set null', select=True),
'partner_id': fields.related('parent_id', type='many2one', relation='res.partner', string='Partner'), # for backward compatibility
'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."),
'function': fields.char('Function', size=128),
'title': fields.many2one('res.partner.title','Title'),
@ -317,83 +448,29 @@ class res_partner_address(osv.osv):
'is_customer_add': fields.related('partner_id', 'customer', type='boolean', string='Customer'),
'is_supplier_add': fields.related('partner_id', 'supplier', type='boolean', string='Supplier'),
'active': fields.boolean('Active', help="Uncheck the active field to hide the contact."),
# 'company_id': fields.related('partner_id','company_id',type='many2one',relation='res.company',string='Company', store=True),
'company_id': fields.many2one('res.company', 'Company',select=1),
'color': fields.integer('Color Index'),
}
_defaults = {
'active': lambda *a: 1,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner.address', context=c),
'active': True,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c),
'color': 0,
'type': 'default',
}
def name_get(self, cr, user, ids, context=None):
if context is None:
context = {}
if not len(ids):
return []
res = []
for r in self.read(cr, user, ids, ['name','zip','country_id', 'city','partner_id', 'street']):
if context.get('contact_display', 'contact')=='partner' and r['partner_id']:
res.append((r['id'], r['partner_id'][1]))
else:
# make a comma-separated list with the following non-empty elements
elems = [r['name'], r['country_id'] and r['country_id'][1], r['city'], r['street']]
addr = ', '.join(filter(bool, elems))
if (context.get('contact_display', 'contact')=='partner_address') and r['partner_id']:
res.append((r['id'], "%s: %s" % (r['partner_id'][1], addr or '/')))
else:
res.append((r['id'], addr or '/'))
return res
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
if not args:
args=[]
if not context:
context={}
if context.get('contact_display', 'contact')=='partner ' or context.get('contact_display', 'contact')=='partner_address ' :
ids = self.search(cr, user, [('partner_id',operator,name)], limit=limit, context=context)
else:
if not name:
ids = self.search(cr, user, args, limit=limit, context=context)
else:
ids = self.search(cr, user, [('zip','=',name)] + args, limit=limit, context=context)
if not ids:
ids = self.search(cr, user, [('city',operator,name)] + args, limit=limit, context=context)
if name:
ids += self.search(cr, user, [('name',operator,name)] + args, limit=limit, context=context)
ids += self.search(cr, user, [('partner_id',operator,name)] + args, limit=limit, context=context)
return self.name_get(cr, user, ids, context=context)
def get_city(self, cr, uid, id):
return self.browse(cr, uid, id).city
def _display_address(self, cr, uid, address, context=None):
'''
The purpose of this function is to build and return an address formatted accordingly to the
standards of the country where it belongs.
:param address: browse record of the res.partner.address to format
:returns: the address formatted in a display that fit its country habits (or the default ones
if not country is specified)
:rtype: string
'''
# get the address format
address_format = address.country_id and address.country_id.address_format or \
'%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s'
# get the information that will be injected into the display format
args = {
'state_code': address.state_id and address.state_id.code or '',
'state_name': address.state_id and address.state_id.name or '',
'country_code': address.country_id and address.country_id.code or '',
'country_name': address.country_id and address.country_id.name or '',
}
address_field = ['title', 'street', 'street2', 'zip', 'city']
for field in address_field :
args[field] = getattr(address, field) or ''
return address_format % args
res_partner_address()
def write(self, cr, uid, ids, vals, context=None):
logging.getLogger('res.partner').warning("Deprecated use of res.partner.address")
if 'partner_id' in vals:
vals['parent_id'] = vals.get('partner_id')
del(vals['partner_id'])
return self.pool.get('res.partner').write(cr, uid, ids, vals, context=context)
def create(self, cr, uid, vals, context=None):
logging.getLogger('res.partner').warning("Deprecated use of res.partner.address")
if 'partner_id' in vals:
vals['parent_id'] = vals.get('partner_id')
del(vals['partner_id'])
return self.pool.get('res.partner').create(cr, uid, vals, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<report id="res_partner_address_report" model="res.partner" name="res.partner.address" string="Labels" xml="base/res/report/partner_address.xml" xsl="base/res/report/partner_address.xsl" groups="base.group_extended"/>
<report id="res_partner_address_report" model="res.partner" name="res.partner" string="Labels" xml="base/res/report/partner_address.xml" xsl="base/res/report/partner_address.xsl" groups="base.group_extended"/>
<!--
<report string="Business Cards" model="res.partner" name="res.partner.businesscard" xml="base/res/report/business_card.xml" xsl="base/res/report/business_card.xsl"/>
-->

View File

@ -6,12 +6,16 @@
web_icon_hover="data/sales-hover.png"
groups="base.group_sale_salesman"/>
<menuitem id="menu_address_book" name="Address Book" parent="menu_base_partner" sequence="2"/>
<menuitem id="base.menu_sales" name="Sales"
parent="base.menu_base_partner" sequence="1"
/>
<!-- <menuitem id="menu_address_book" name="Address Book" parent="menu_base_partner" sequence="2"/> -->
<menuitem id="menu_base_config" name="Configuration" parent="menu_base_partner" sequence="30"
groups="group_system"/>
<menuitem id="menu_config_address_book" name="Address Book" parent="menu_base_config" sequence="2"
<menuitem id="menu_config_address_book" name="Address Book" parent="menu_base_config" sequence="40"
groups="group_system"/>
<!--
@ -175,7 +179,7 @@
</kanban>
</field>
</record>
<record id="action_partner_address_form" model="ir.actions.act_window">
<field name="name">Addresses</field>
<field name="type">ir.actions.act_window</field>
@ -198,9 +202,9 @@
<field name="view_id" ref="view_partner_address_form1"/>
<field name="act_window_id" ref="action_partner_address_form"/>
</record>
<menuitem action="action_partner_address_form" id="menu_partner_address_form"
<!--menuitem action="action_partner_address_form" id="menu_partner_address_form"
groups="base.group_extended" name="Contacts"
parent="base.menu_address_book" sequence="30"/>
parent="base.menu_address_book" sequence="30"/-->
<!--
=========================================
@ -285,7 +289,7 @@
<field name="help">Manage the partner titles you want to have available in your system. The partner titles is the legal status of the company: Private Limited, SA, etc.</field>
</record>
<menuitem action="action_partner_title_partner" id="menu_partner_title_partner" parent="menu_config_address_book" sequence="2"/>
<menuitem action="action_partner_title_partner" id="menu_partner_title_partner" parent="menu_config_address_book" sequence="2" groups="base.group_no_one"/>
<record id="action_partner_title_contact" model="ir.actions.act_window">
<field name="name">Contact Titles</field>
@ -297,7 +301,7 @@
<field name="help">Manage the contact titles you want to have available in your system and the way you want to print them in letters and other documents. Some example: Mr., Mrs. </field>
</record>
<menuitem action="action_partner_title_contact" id="menu_partner_title_contact" name="Contact Titles" parent="menu_config_address_book" sequence="3"/>
<menuitem action="action_partner_title_contact" id="menu_partner_title_contact" name="Contact Titles" parent="menu_config_address_book" sequence="3" groups="base.group_no_one"/>
<!--
=======================
Partner
@ -309,14 +313,15 @@
<field name="type">tree</field>
<field eval="8" name="priority"/>
<field name="arch" type="xml">
<tree string="Partners">
<tree string="Contacts">
<field name="name"/>
<field name="ref" groups="base.group_extended"/>
<field name="function" invisible="1"/>
<field name="phone"/>
<field name="email"/>
<field name="city"/>
<field name="country"/>
<field name="user_id"/>
<field name="user_id" invisible="1"/>
<field name="is_company" invisible="1"/>
<field name="country" invisible="1"/>
<field name="country_id" invisible="1"/>
</tree>
</field>
</record>
@ -326,76 +331,140 @@
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Partners" col='1'>
<group col="6" colspan="4">
<group colspan="5" col="6">
<field name="name" select="1"/>
<field name="ref" groups="base.group_extended"/>
<field domain="[('domain', '=', 'partner')]" name="title" size="0" groups="base.group_extended"/>
<field name="lang"/>
<form string="Partners">
<group col="8" colspan="4">
<group col="4" colspan="4">
<field name="name" required="1"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}" colspan="4"/>
<field name="parent_id" string="Company" colspan="4" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<group colspan="1" col="2">
<field name="customer" select="1"/>
<group col="2">
<field name="is_company" on_change="onchange_type(is_company)"/>
</group>
<group col="2">
<field name="customer"/>
<field name="supplier"/>
<!-- <field name="employee"/>-->
</group>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
</group>
<notebook colspan="4">
<page string="General">
<field colspan="4" mode="form,tree" name="address" nolabel="1" select="1" height="260">
<form string="Partner Contacts">
<group colspan="4" col="6">
<field name="name" string="Contact Name"/>
<field domain="[('domain', '=', 'contact')]" name="title" size="0"/>
<field name="function"/>
<group colspan="2">
<separator string="Address" colspan="4"/>
<field name="type" string="Type" attrs="{'invisible': [('is_company','=', True)]}"/>
<group colspan="2">
<field name="use_parent_address" attrs="{'invisible': [('parent_id', '=', False)]}"
on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<newline/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="state_id"/>
</group>
<group colspan="2">
<separator string="Communication" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="phone" colspan="4"/>
<field name="mobile" colspan="4"/>
<field name="fax" colspan="4"/>
<field name="email" widget="email" colspan="4"/>
<field name="website" widget="url" colspan="4"/>
<field name="ref" groups="base.group_extended" colspan="4"/>
</group>
<group colspan="4" attrs="{'invisible': [('is_company','=', False)]}">
<field name="child_ids" context="{'default_parent_id': active_id}" nolabel="1">
<form string="Partners">
<group col="8" colspan="4">
<group col="4" colspan="4">
<field name="name" required="1"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}" colspan="4"/>
<field name="parent_id" string="Company" colspan="4" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)" invisible="1"/>
</group>
<group col="2">
<field name="is_company" on_change="onchange_type(is_company)" invisible="1"/>
</group>
<group col="2">
<field name="customer"/>
<field name="supplier"/>
</group>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
</group>
<newline/>
<group colspan="2" col="4">
<separator string="Postal Address" colspan="4" col="4" />
<field name="type" string="Type" colspan="2"/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id" completion="1"/>
<field name="state_id"/>
</group>
<group colspan="2" col="2">
<separator string="Communication" colspan="2" col="2" />
<field name="phone"/>
<field name="mobile"/>
<field name="fax"/>
<field name="email" widget="email"/>
</group>
</form>
<tree string="Partner Contacts">
<field name="name"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="phone"/>
<field name="email"/>
</tree>
</field>
<group groups="base.group_extended">
<separator colspan="4" string="Categories"/>
<field colspan="4" name="category_id" nolabel="1"/>
<notebook colspan="4">
<page string="General">
<group colspan="2">
<separator string="Address" colspan="4"/>
<field name="type" string="Type" attrs="{'invisible': [('is_company','=', True)]}"/>
<group colspan="2">
<field name="use_parent_address" attrs="{'invisible': [('is_company','=', True)]}" on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<newline/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="state_id"/>
</group>
<group colspan="2">
<separator string="Communication" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="phone" colspan="4"/>
<field name="mobile" colspan="4"/>
<field name="fax" colspan="4"/>
<field name="email" widget="email" colspan="4"/>
<field name="website" widget="url" colspan="4"/>
<field name="ref" groups="base.group_extended" colspan="4"/>
</group>
</page>
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<separator string="General Information" colspan="4"/>
<field name="user_id"/>
<field name="active" groups="base.group_extended"/>
<field name="date"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<newline/>
</page>
<page string="Categories" groups="base.group_extended">
<field name="category_id" colspan="4" nolabel="1"/>
</page>
<page string="Notes">
<field name="comment" colspan="4" nolabel="1"/>
</page>
</notebook>
</form>
</field>
</group>
</page>
<page string="Sales &amp; Purchases">
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<separator string="General Information" colspan="4"/>
<field name="user_id"/>
<field name="active" groups="base.group_extended"/>
<field name="website" widget="url"/>
<field name="date"/>
<field name="parent_id" groups="base.group_extended"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<newline/>
</page>
<page string="History" groups="base.group_extended" invisible="True">
</page>
<page string="Categories" groups="base.group_extended">
<field name="category_id" colspan="4" nolabel="1"/>
</page>
<page string="Notes">
<field colspan="4" name="comment" nolabel="1"/>
<field name="comment" colspan="4" nolabel="1"/>
</page>
</notebook>
</form>
@ -409,12 +478,15 @@
<field name="arch" type="xml">
<search string="Search Partner">
<group col='10' colspan='4'>
<filter string="Persons" name="type_person" icon="terp-personal" domain="[('is_company','=',0)]"/>
<filter string="Companies" name="type_company" icon="terp-partner" domain="[('is_company','=',1)]"/>
<separator orientation="vertical"/>
<filter string="Customers" name="customer" icon="terp-personal" domain="[('customer','=',1)]" help="Customer Partners"/>
<filter string="Suppliers" name="supplier" icon="terp-personal" domain="[('supplier','=',1)]" help="Supplier Partners"/>
<separator orientation="vertical"/>
<field name="name" select="1"/>
<field name="address" select="1"/>
<field name="country" select="1"/>
<!--field name="address" select="1"/-->
<!--field name="country" select="1"/-->
<field name="category_id" select="1" groups="base.group_extended"/>
<field name="user_id" select="1">
<filter help="My Partners" icon="terp-personal+" domain="[('user_id','=',uid)]"/>
@ -423,6 +495,7 @@
<newline />
<group expand="0" string="Group By...">
<filter string="Salesman" icon="terp-personal" domain="[]" context="{'group_by' : 'user_id'}" />
<filter string="Company" context="{'group_by': 'parent_id'}"/>
</group>
</search>
</field>
@ -430,66 +503,69 @@
<!-- Partner Kanban View -->
<record model="ir.ui.view" id="res_partner_kanban_view">
<field name="name">RES - PARTNER KANBAN</field>
<field name="name">res.partner.kanban</field>
<field name="model">res.partner</field>
<field name="type">kanban</field>
<field name="arch" type="xml">
<kanban>
<field name="color"/>
<field name="name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="photo"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="state_id"/>
<templates>
<t t-name="kanban-box">
<t t-set="color" t-value="kanban_color(record.color.raw_value || record.name.raw_value)"/>
<div t-att-class="color + (record.color.raw_value == 1 ? ' oe_kanban_color_alert' : '')">
<div class="oe_kanban_box oe_kanban_color_border">
<div class="oe_kanban_box_header oe_kanban_color_bgdark oe_kanban_color_border oe_kanban_draghandle">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
<field name="name"/>
</td>
<td valign="top" width="22">
<img t-att-src="kanban_gravatar(record.email.value, 22)" class="oe_kanban_gravatar"/>
</td>
</tr>
</table>
</div>
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
<table class="oe_kanban_table">
<tr>
<td valign="top" width="64" align="left">
<img src="/base/static/src/img/kanban_partner.png" width="64" height="64"/>
</td>
<td valign="top" align="left">
<div class="oe_kanban_title2">
<field name="title"/>
<t t-if="record.title.raw_value and record.country.raw_value">,</t>
<field name="country"/>
</div>
<div class="oe_kanban_title3">
<field name="subname"/>
<t t-if="record.subname.raw_value and record.function.raw_value">,</t>
<field name="function"/>
</div>
<div class="oe_kanban_title3">
<i><field name="email"/>
<t t-if="record.phone.raw_value and record.email.raw_value">,</t>
<field name="phone"/></i>
</div>
</td>
</tr>
</table>
</div>
<div class="oe_kanban_buttons_set oe_kanban_color_border oe_kanban_color_bglight oe_kanban_box_show_onclick">
<div class="oe_kanban_left">
<a string="Edit" icon="gtk-edit" type="edit"/>
<a string="Change Color" icon="color-picker" type="color" name="color"/>
<a title="Mail" t-att-href="'mailto:'+record.email.value" style="text-decoration: none;" >
<img src="/web/static/src/img/icons/terp-mail-message-new.png" border="0" width="16" height="16"/>
</a>
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>
<div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '')">
<div class="oe_module_vignette">
<a type="edit">
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" width="64" height="64" class="oe_module_icon"/>
</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">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
<h4><a type="edit"><field name="name"/></a>
<div t-if="record.parent_id.raw_value"><field name="parent_id"/></div>
</h4>
<i><div t-if="record.contact_address.raw_value"><field name="contact_address"/><br/></div>
<div t-if="record.email.raw_value">
<field name="email"/><br/></div>
<div t-if="record.mobile.raw_value">
<field name="mobile"/><br/>
</div>
<div t-if="!record.mobile.raw_value and record.phone.raw_value">
<field name="phone"/><br/>
</div></i>
</td>
<td t-if="record.is_company.raw_value" valign="top" align="right">
<!--img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" class="oe_kanban_gravatar"/-->
</td>
</tr>
</table>
</div>
<br class="oe_kanban_clear"/>
<div class="oe_kanban_buttons_set oe_kanban_color_border oe_kanban_color_bglight oe_kanban_box_show_onclick">
<div class="oe_kanban_left">
<a string="Edit" icon="gtk-edit" type="edit"/>
<a string="Change Color" icon="color-picker" type="color" name="color"/>
<a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value" style="text-decoration: none;" >
<img src="/web/static/src/img/icons/terp-mail-message-new.png" border="0" width="16" height="16"/>
</a>
</div>
<br class="oe_kanban_clear"/>
</div>
</div>
</div>
</div>
@ -500,32 +576,34 @@
</record>
<record id="action_partner_form" model="ir.actions.act_window">
<field name="name">Customers</field>
<field name="name">Contacts</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner</field>
<field name="view_type">form</field>
<field name="view_mode">kanban</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context">{"search_default_customer":1}</field>
<field name="search_view_id" ref="view_res_partner_filter"/>
<field name="help">A customer is an entity you do business with, like a company or an organization. A customer can have several contacts or addresses which are the people working for this company. You can use the history tab, to follow all transactions related to a customer: sales order, emails, opportunities, claims, etc. If you use the email gateway, the Outlook or the Thunderbird plugin, don't forget to register emails to each contact so that the gateway will automatically attach incoming emails to the right partner.</field>
</record>
<record id="action_partner_form_view1" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_partner_tree"/>
<field eval="0" name="sequence"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="res_partner_kanban_view"/>
<field name="act_window_id" ref="action_partner_form"/>
</record>
<record id="action_partner_form_view2" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_partner_form"/>
<field name="act_window_id" ref="action_partner_form"/>
</record>
<menuitem
action="action_partner_form"
id="menu_partner_form"
parent="base.menu_address_book"
sequence="2"/>
<record id="action_partner_tree_view1" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_partner_tree"/>
<field name="act_window_id" ref="action_partner_form"/>
</record>
<menuitem id="menu_partner_form" parent="base.menu_sales" action="action_partner_form" sequence="1"/>
<record id="action_partner_customer_form" model="ir.actions.act_window">
<field name="name">Customers</field>
@ -663,7 +741,7 @@
<field name="help">Manage the partner categories in order to better classify them for tracking and analysis purposes. A partner may belong to several categories and categories have a hierarchy structure: a partner belonging to a category also belong to his parent category.</field>
</record>
<menuitem action="action_partner_category_form" id="menu_partner_category_form" name="Partner Categories" sequence="4" parent="menu_config_address_book" groups="base.group_extended"/>
<menuitem action="action_partner_category_form" id="menu_partner_category_form" name="Partner Categories" sequence="4" parent="menu_config_address_book" groups="base.group_no_one"/>
<act_window domain="[('partner_id', '=', active_id)]" context="{'default_partner_id':active_id}"
id="act_res_partner_event" name="Events"

View File

@ -35,8 +35,8 @@
<field name="name">user rule</field>
<field model="ir.model" name="model_id" ref="model_res_users"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id.child_ids','child_of',[user.company_id.id]),('company_id','child_of',[user.company_id.id])]</field>
<field name="domain_force">[('company_ids','child_of',[user.company_id.id])]</field>
</record>
</data>
</data>
</openerp>

View File

@ -25,15 +25,21 @@ from functools import partial
import pytz
import io, StringIO
from lxml import etree
from lxml.builder import E
import netsvc
import pooler
import tools
from osv import fields,osv
from osv.orm import browse_record
from service import security
from tools.translate import _
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
import tools
from tools.translate import _
_logger = logging.getLogger(__name__)
@ -107,21 +113,6 @@ class groups(osv.osv):
aid.write({'groups_id': [(4, gid)]})
return gid
def unlink(self, cr, uid, ids, context=None):
group_users = []
for record in self.read(cr, uid, ids, ['users'], context=context):
if record['users']:
group_users.extend(record['users'])
if group_users:
user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)]
user_names = list(set(user_names))
if len(user_names) >= 5:
user_names = user_names[:5] + ['...']
raise osv.except_osv(_('Warning !'),
_('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \
', '.join(user_names))
return super(groups, self).unlink(cr, uid, ids, context=context)
def get_extended_interface_group(self, cr, uid, context=None):
data_obj = self.pool.get('ir.model.data')
extended_group_data_id = data_obj._get_id(cr, uid, 'base', 'group_extended')
@ -200,7 +191,6 @@ class users(osv.osv):
self.write(cr, uid, ids, {'groups_id': [(4, extended_group_id)]}, context=context)
return True
def _get_interface_type(self, cr, uid, ids, name, args, context=None):
"""Implementation of 'view' function field getter, returns the type of interface of the users.
@param field_name: Name of the field
@ -212,6 +202,33 @@ class users(osv.osv):
extended_users = group_obj.read(cr, uid, extended_group_id, ['users'], context=context)['users']
return dict(zip(ids, ['extended' if user in extended_users else 'simple' for user in ids]))
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.
@ -241,6 +258,11 @@ 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",
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."),
'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."),
@ -352,9 +374,15 @@ 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)
_defaults = {
'password' : '',
'context_lang': 'en_US',
'avatar': _get_avatar,
'active' : True,
'menu_id': _get_menu,
'company_id': _get_company,
@ -743,31 +771,28 @@ class groups_view(osv.osv):
# and introduces the reified group fields
view = self.get_user_groups_view(cr, uid, context)
if view:
xml = u"""<?xml version="1.0" encoding="utf-8"?>
<!-- GENERATED AUTOMATICALLY BY GROUPS -->
<field name="groups_id" position="replace">
%s
%s
</field>
"""
xml1, xml2 = [], []
xml1.append('<separator string="%s" colspan="4"/>' % _('Applications'))
xml1.append(E.separator(string=_('Application'), colspan="4"))
for app, kind, gs in self.get_groups_by_application(cr, uid, context):
# hide groups in category 'Hidden' (except to group_no_one)
attrs = 'groups="base.group_no_one"' if app and app.xml_id == 'base.module_category_hidden' else ''
attrs = {'groups': 'base.group_no_one'} if app and app.xml_id == 'base.module_category_hidden' else {}
if kind == 'selection':
# application name with a selection field
field_name = name_selection_groups(map(int, gs))
xml1.append('<field name="%s" %s/>' % (field_name, attrs))
xml1.append('<newline/>')
xml1.append(E.field(name=field_name, **attrs))
xml1.append(E.newline())
else:
# application separator with boolean fields
app_name = app and app.name or _('Other')
xml2.append('<separator string="%s" colspan="4" %s/>' % (app_name, attrs))
xml2.append(E.separator(string=app_name, colspan="4", **attrs))
for g in gs:
field_name = name_boolean_group(g.id)
xml2.append('<field name="%s" %s/>' % (field_name, attrs))
view.write({'arch': xml % ('\n'.join(xml1), '\n'.join(xml2))})
xml2.append(E.field(name=field_name, **attrs))
xml = E.field(*(xml1 + xml2), name="groups_id", position="replace")
xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
xml_content = etree.tostring(xml, pretty_print=True, xml_declaration=True, encoding="utf-8")
view.write({'arch': xml_content})
return True
def get_user_groups_view(self, cr, uid, context=None):

View File

@ -60,7 +60,10 @@ class partner_massmail_wizard(osv.osv_memory):
ir_mail_server = self.pool.get('ir.mail_server')
emails_seen = set()
for partner in partners:
for adr in partner.address:
for adr in partner.child_ids:
if adr.is_company:
#we don't want to consider child companies but only the contacts
continue
if adr.email and not adr.email in emails_seen:
try:
emails_seen.add(adr.email)

View File

@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_res_partner_address_group_partner_manager","res_partner_address group_partner_manager","model_res_partner_address","group_partner_manager",1,1,1,1
"access_res_partner_address_group_user","res_partner_address group_user","model_res_partner_address","group_user",1,0,0,0
"access_res_partner_address","res.partner.address","model_res_partner_address","group_system",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_partner_address_group_partner_manager res_partner_address group_partner_manager model_res_partner_address group_partner_manager 1 1 1 1
3 access_res_partner_address_group_user res_partner_address group_user model_res_partner_address group_user 1 0 0 0
4 access_res_partner_address res.partner.address model_res_partner_address group_system 1 1 1 1

View File

@ -55,8 +55,6 @@
"access_res_lang_group_user","res_lang group_user","model_res_lang","group_system",1,1,1,1
"access_res_partner_group_partner_manager","res_partner group_partner_manager","model_res_partner","group_partner_manager",1,1,1,1
"access_res_partner_group_user","res_partner group_user","model_res_partner","group_user",1,0,0,0
"access_res_partner_address_group_partner_manager","res_partner_address group_partner_manager","model_res_partner_address","group_partner_manager",1,1,1,1
"access_res_partner_address_group_user","res_partner_address group_user","model_res_partner_address","group_user",1,0,0,0
"access_res_partner_bank_group_user","res_partner_bank group_user","model_res_partner_bank","group_user",1,0,0,0
"access_res_partner_bank_group_partner_manager","res_partner_bank group_partner_manager","model_res_partner_bank","group_partner_manager",1,1,1,1
"access_res_partner_bank_type_group_partner_manager","res_partner_bank_type group_partner_manager","model_res_partner_bank_type","group_partner_manager",1,1,1,1
@ -117,7 +115,6 @@
"access_ir_filter all","ir_filters all","model_ir_filters",,1,0,0,0
"access_ir_filter employee","ir_filters employee","model_ir_filters","group_user",1,1,1,1
"access_ir_filters","ir_filters_all","model_ir_filters",,1,1,1,1
"access_res_partner_address","res.partner.address","model_res_partner_address","group_system",1,1,1,1
"access_res_widget","res.widget","model_res_widget","group_erp_manager",1,1,1,1
"access_res_widget_user","res.widget.user","model_res_widget",,1,0,0,0
"access_res_log_all","res.log","model_res_log",,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
55 access_res_lang_group_user res_lang group_user model_res_lang group_system 1 1 1 1
56 access_res_partner_group_partner_manager res_partner group_partner_manager model_res_partner group_partner_manager 1 1 1 1
57 access_res_partner_group_user res_partner group_user model_res_partner group_user 1 0 0 0
access_res_partner_address_group_partner_manager res_partner_address group_partner_manager model_res_partner_address group_partner_manager 1 1 1 1
access_res_partner_address_group_user res_partner_address group_user model_res_partner_address group_user 1 0 0 0
58 access_res_partner_bank_group_user res_partner_bank group_user model_res_partner_bank group_user 1 0 0 0
59 access_res_partner_bank_group_partner_manager res_partner_bank group_partner_manager model_res_partner_bank group_partner_manager 1 1 1 1
60 access_res_partner_bank_type_group_partner_manager res_partner_bank_type group_partner_manager model_res_partner_bank_type group_partner_manager 1 1 1 1
115 access_ir_filter all ir_filters all model_ir_filters 1 0 0 0
116 access_ir_filter employee ir_filters employee model_ir_filters group_user 1 1 1 1
117 access_ir_filters ir_filters_all model_ir_filters 1 1 1 1
access_res_partner_address res.partner.address model_res_partner_address group_system 1 1 1 1
118 access_res_widget res.widget model_res_widget group_erp_manager 1 1 1 1
119 access_res_widget_user res.widget.user model_res_widget 1 0 0 0
120 access_res_log_all res.log model_res_log 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -68,20 +68,20 @@
-
Testing that some domain expressions work
-
!python {model: res.partner.address }: |
ids = self.search(cr, uid, [('partner_id','=','Agrolait')])
!python {model: res.partner }: |
ids = self.search(cr, uid, [('parent_id','=','Agrolait')])
assert len(ids) >= 1, ids
-
Trying the "in" operator, for scalar value
-
!python {model: res.partner.address }: |
ids = self.search(cr, uid, [('partner_id','in','Agrolait')])
!python {model: res.partner }: |
ids = self.search(cr, uid, [('parent_id','in','Agrolait')])
assert len(ids) >= 1, ids
-
Trying the "in" operator for list value
-
!python {model: res.partner.address }: |
ids = self.search(cr, uid, [('partner_id','in',['Agrolait','ASUStek'])])
!python {model: res.partner }: |
ids = self.search(cr, uid, [('parent_id','in',['Agrolait','ASUStek'])])
assert len(ids) >= 1, ids
-
Check we can use "in" operator for plain fields.
@ -92,11 +92,11 @@
-
Test one2many operator with empty search list
-
!assert {model: res.partner, search: "[('address', 'in', [])]", count: 0, string: "Ids should be empty"}
!assert {model: res.partner, search: "[('child_ids', 'in', [])]", count: 0, string: "Ids should be empty"}
-
Test one2many operator with False
-
!assert {model: res.partner, search: "[('address', '=', False)]"}:
!assert {model: res.partner, search: "[('child_ids', '=', False)]"}:
- address in (False, None, [])
-
Test many2many operator with empty search list
@ -110,7 +110,7 @@
-
Filtering on invalid value across x2many relationship should return an empty set
-
!assert {model: res.partner, search: "[('address.city','=','foo')]", count: 0, string: "Searching for address.city = foo should give empty results"}
!assert {model: res.partner, search: "[('child_ids.city','=','foo')]", count: 0, string: "Searching for address.city = foo should give empty results"}
-
Check if many2one works with empty search list
-
@ -515,7 +515,7 @@
vals = {'category_id': [(6, 0, [ref("base.res_partner_category_8")])],
'name': 'OpenERP Test',
'active': False,
'address': [(0, 0, {'country_id': ref("base.be")})]
'child_ids': [(0, 0, {'name': 'address of OpenERP Test', 'country_id': ref("base.be")})]
}
self.create(cr, uid, vals, context=context)
res_ids = self.search(cr, uid, [('category_id', 'ilike', 'supplier'), ('active', '=', False)])
@ -524,6 +524,6 @@
Testing for One2Many field with country Belgium and active=False
-
!python {model: res.partner }: |
res_ids = self.search(cr, uid, [('address.country_id','=','Belgium'),('active','=',False)])
res_ids = self.search(cr, uid, [('child_ids.country_id','=','Belgium'),('active','=',False)])
assert len(res_ids) != 0, "Record not Found with country Belgium and active False."

View File

@ -24,41 +24,25 @@
"""
import base64
import imp
import itertools
import logging
import os
import re
import sys
import threading
import zipfile
import zipimport
from cStringIO import StringIO
from os.path import join as opj
from zipfile import PyZipFile, ZIP_DEFLATED
import openerp
import openerp.modules.db
import openerp.modules.graph
import openerp.modules.migration
import openerp.netsvc as netsvc
import openerp.osv as osv
import openerp.pooler as pooler
import openerp.release as release
import openerp.tools as tools
import openerp.tools.osutil as osutil
import openerp.tools.assertion_report as assertion_report
from openerp.tools.safe_eval import safe_eval as eval
from openerp import SUPERUSER_ID
from openerp.tools.translate import _
from openerp.modules.module import \
get_modules, get_modules_with_version, \
load_information_from_description_file, \
get_module_resource, zip_directory, \
get_module_path, initialize_sys_path, \
from openerp.modules.module import initialize_sys_path, \
load_openerp_module, init_module_models
_logger = logging.getLogger(__name__)
@ -99,7 +83,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
threading.currentThread().testing = True
_load_data(cr, module_name, idref, mode, 'test')
return True
except Exception, e:
except Exception:
_logger.error(
'module %s: an exception occurred in a test', module_name)
return False
@ -178,7 +162,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
modobj = pool.get('ir.module.module')
if perform_checks:
modobj.check(cr, 1, [module_id])
modobj.check(cr, SUPERUSER_ID, [module_id])
idref = {}
@ -189,7 +173,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
load_init_xml(module_name, idref, mode)
load_update_xml(module_name, idref, mode)
load_data(module_name, idref, mode)
@ -218,9 +202,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
ver = release.major_version + '.' + package.data['version']
# Set new modules and dependencies
modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
# Update translations for all installed languages
modobj.update_translations(cr, 1, [module_id], None)
modobj.update_translations(cr, SUPERUSER_ID, [module_id], None)
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
@ -257,7 +241,7 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m
while True:
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
module_list = [name for (name,) in cr.fetchall() if name not in graph]
new_modules_in_graph = graph.add_modules(cr, module_list, force)
graph.add_modules(cr, module_list, force)
_logger.debug('Updating graph with %d more modules', len(module_list))
loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
processed_modules.extend(processed)
@ -321,15 +305,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
if ids:
modobj.button_install(cr, 1, ids)
modobj.button_install(cr, SUPERUSER_ID, ids)
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
if ids:
modobj.button_upgrade(cr, 1, ids)
modobj.button_upgrade(cr, SUPERUSER_ID, ids)
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
@ -339,7 +323,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# partially installed modules (i.e. installed/to upgrade), to
# offer a consistent system to the second part: installing
# newly selected modules.
states_to_load = ['installed', 'to upgrade']
# We include the modules 'to remove' in the first step, because
# they are part of the "currently installed" modules. They will
# be dropped in STEP 6 later, before restarting the loading
# process.
states_to_load = ['installed', 'to upgrade', 'to remove']
processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
processed_modules.extend(processed)
if update_module:
@ -350,9 +338,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# load custom models
cr.execute('select model from ir_model where state=%s', ('manual',))
for model in cr.dictfetchall():
pool.get('ir.model').instanciate(cr, 1, model['model'], {})
pool.get('ir.model').instanciate(cr, SUPERUSER_ID, model['model'], {})
# STEP 4: Finish and cleanup
# STEP 4: Finish and cleanup installations
if processed_modules:
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
for (model, name) in cr.fetchall():
@ -377,33 +365,18 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
_logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
# Cleanup orphan records
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
cr.commit()
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
for mod_id, mod_name in cr.fetchall():
cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
for rmod, rid in cr.fetchall():
uid = 1
rmod_module= pool.get(rmod)
if rmod_module:
# TODO group by module so that we can delete multiple ids in a call
rmod_module.unlink(cr, uid, [rid])
else:
_logger.error('Could not locate %s to remove res=%d' % (rmod,rid))
cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
cr.commit()
# Remove menu items that are not referenced by any of other
# (child) menu item, ir_values, or ir_model_data.
# This code could be a method of ir_ui_menu.
# TODO: remove menu without actions of children
# STEP 5: Cleanup menus
# Remove menu items that are not referenced by any of other
# (child) menu item, ir_values, or ir_model_data.
# TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
if update_module:
while True:
cr.execute('''delete from
ir_ui_menu
@ -419,9 +392,19 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
else:
_logger.info('removed %d unused menus', cr.rowcount)
# Pretend that modules to be removed are actually uninstalled.
cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
cr.commit()
# STEP 6: Uninstall modules to remove
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
mod_ids_to_remove = [x[0] for x in cr.fetchall()]
if mod_ids_to_remove:
pool.get('ir.module.module').module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
# Recursive reload, should only happen once, because there should be no
# modules to remove next time
cr.commit()
_logger.info('Reloading registry once more after uninstalling modules')
return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
if report.failures:
_logger.error('At least one test failed when loading the modules.')

View File

@ -159,32 +159,6 @@ class integer(_column):
" `required` has no effect, as NULL values are "
"automatically turned into 0.")
class integer_big(_column):
"""Experimental 64 bit integer column type, currently unused.
TODO: this field should work fine for values up
to 32 bits, but greater values will not fit
in the XML-RPC int type, so a specific
get() method is needed to pass them as floats,
like what we do for integer functional fields.
"""
_type = 'integer_big'
# do not reference the _symbol_* of integer class, as that would possibly
# unbind the lambda functions
_symbol_c = '%s'
_symbol_f = lambda x: int(x or 0)
_symbol_set = (_symbol_c, _symbol_f)
_symbol_get = lambda self,x: x or 0
_deprecated = True
def __init__(self, string='unknown', required=False, **args):
super(integer_big, self).__init__(string=string, required=required, **args)
if required:
_logger.debug(
"required=True is deprecated: making an integer_big field"
" `required` has no effect, as NULL values are "
"automatically turned into 0.")
class reference(_column):
_type = 'reference'
_classic_read = False # post-process to handle missing target
@ -347,20 +321,6 @@ class datetime(_column):
exc_info=True)
return timestamp
class time(_column):
_type = 'time'
_deprecated = True
@staticmethod
def now( *args):
""" Returns the current time in a format fit for being a
default value to a ``time`` field.
This method should be proivided as is to the _defaults dict,
it should not be called.
"""
return DT.datetime.now().strftime(
tools.DEFAULT_SERVER_TIME_FORMAT)
class binary(_column):
_type = 'binary'
_symbol_c = '%s'
@ -426,34 +386,6 @@ class selection(_column):
# (4, ID) link
# (5) unlink all (only valid for one2many)
#
#CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
class one2one(_column):
_classic_read = False
_classic_write = True
_type = 'one2one'
_deprecated = True
def __init__(self, obj, string='unknown', **args):
_logger.warning("The one2one field is deprecated and doesn't work anymore.")
_column.__init__(self, string=string, **args)
self._obj = obj
def set(self, cr, obj_src, id, field, act, user=None, context=None):
if not context:
context = {}
obj = obj_src.pool.get(self._obj)
self._table = obj_src.pool.get(self._obj)._table
if act[0] == 0:
id_new = obj.create(cr, user, act[1])
cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
else:
cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
id = cr.fetchone()[0]
obj.write(cr, user, [id], act[1], context=context)
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
class many2one(_column):
_classic_read = False
@ -1079,7 +1011,7 @@ class function(_column):
self._symbol_f = boolean._symbol_f
self._symbol_set = boolean._symbol_set
if type in ['integer','integer_big']:
if type == 'integer':
self._symbol_c = integer._symbol_c
self._symbol_f = integer._symbol_f
self._symbol_set = integer._symbol_set
@ -1119,7 +1051,7 @@ class function(_column):
elif not context.get('bin_raw'):
result = sanitize_binary_value(value)
if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
if field_type == "integer" and value > xmlrpclib.MAXINT:
# integer/long values greater than 2^31-1 are not supported
# in pure XMLRPC, so we have to pass them as floats :-(
# This is not needed for stored fields and non-functional integer
@ -1588,7 +1520,7 @@ def field_to_dict(model, cr, user, field, context=None):
else:
# call the 'dynamic selection' function
res['selection'] = field.selection(model, cr, user, context)
if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
if res['type'] in ('one2many', 'many2many', 'many2one'):
res['relation'] = field._obj
res['domain'] = field._domain
res['context'] = field._context

View File

@ -70,6 +70,11 @@ _schema = logging.getLogger(__name__ + '.schema')
# List of etree._Element subclasses that we choose to ignore when parsing XML.
from openerp.tools import SKIPPED_ELEMENT_TYPES
# Prefixes for external IDs of schema elements
EXT_ID_PREFIX_FK = "_foreign_key_"
EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_"
EXT_ID_PREFIX_CONSTRAINT = "_constraint_"
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
@ -100,10 +105,10 @@ def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False
if node.get('states'):
if 'invisible' in modifiers and isinstance(modifiers['invisible'], list):
# TODO combine with AND or OR, use implicit AND for now.
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
# TODO combine with AND or OR, use implicit AND for now.
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
else:
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
for a in ('invisible', 'readonly', 'required'):
if node.get(a):
@ -413,7 +418,7 @@ class browse_record(object):
for result_line in field_values:
new_data = {}
for field_name, field_column in fields_to_fetch:
if field_column._type in ('many2one', 'one2one'):
if field_column._type == 'many2one':
if result_line[field_name]:
obj = self._table.pool.get(field_column._obj)
if isinstance(result_line[field_name], (list, tuple)):
@ -544,10 +549,8 @@ def pg_varchar(size=0):
FIELDS_TO_PGTYPES = {
fields.boolean: 'bool',
fields.integer: 'int4',
fields.integer_big: 'int8',
fields.text: 'text',
fields.date: 'date',
fields.time: 'time',
fields.datetime: 'timestamp',
fields.binary: 'bytea',
fields.many2one: 'int4',
@ -866,7 +869,10 @@ class BaseModel(object):
parent_names = [parent_names]
else:
name = cls._name
# for res.parnter.address compatiblity, should be remove in v7
if 'res.partner.address' in parent_names:
parent_names.pop(parent_names.index('res.partner.address'))
parent_names.append('res.partner')
if not name:
raise TypeError('_name is mandatory in case of multiple inheritance')
@ -904,6 +910,7 @@ class BaseModel(object):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
@ -1527,11 +1534,11 @@ class BaseModel(object):
for id, field, field_value in res:
if field in fields_list:
fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
if fld_def._type in ('many2one', 'one2one'):
if fld_def._type == 'many2one':
obj = self.pool.get(fld_def._obj)
if not obj.search(cr, uid, [('id', '=', field_value or False)]):
continue
if fld_def._type in ('many2many'):
if fld_def._type == 'many2many':
obj = self.pool.get(fld_def._obj)
field_value2 = []
for i in range(len(field_value)):
@ -1540,18 +1547,18 @@ class BaseModel(object):
continue
field_value2.append(field_value[i])
field_value = field_value2
if fld_def._type in ('one2many'):
if fld_def._type == 'one2many':
obj = self.pool.get(fld_def._obj)
field_value2 = []
for i in range(len(field_value)):
field_value2.append({})
for field2 in field_value[i]:
if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
if field2 in obj._columns.keys() and obj._columns[field2]._type == 'many2one':
obj2 = self.pool.get(obj._columns[field2]._obj)
if not obj2.search(cr, uid,
[('id', '=', field_value[i][field2])]):
continue
elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type == 'many2one':
obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
if not obj2.search(cr, uid,
[('id', '=', field_value[i][field2])]):
@ -2714,6 +2721,14 @@ class BaseModel(object):
_schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
self._table, column['attname'])
# quick creation of ir.model.data entry to make uninstall of schema elements easier
def _make_ext_id(self, cr, ext_id):
cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module))
if not cr.rowcount:
cr.execute("""INSERT INTO ir_model_data (name,date_init,date_update,module,model)
VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', %s, %s)""",
(ext_id, self._module, self._name))
# checked version: for direct m2o starting from `self`
def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete):
assert self.is_transient() or not dest_model.is_transient(), \
@ -2804,7 +2819,6 @@ class BaseModel(object):
update_custom_fields = context.get('update_custom_fields', False)
self._field_create(cr, context=context)
create = not self._table_exist(cr)
if getattr(self, '_auto', True):
if create:
@ -3039,7 +3053,7 @@ class BaseModel(object):
cr.commit() # start a new transaction
self._add_sql_constraints(cr)
self._add_sql_constraints(cr, context["module"])
if create:
self._execute_sql(cr)
@ -3050,11 +3064,11 @@ class BaseModel(object):
return todo_end
def _auto_end(self, cr, context=None):
""" Create the foreign keys recorded by _auto_init. """
for t, k, r, d in self._foreign_keys:
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
self._make_ext_id(cr, "%s%s_%s_fkey" % (EXT_ID_PREFIX_FK, t, k))
cr.commit()
del self._foreign_keys
@ -3133,6 +3147,7 @@ class BaseModel(object):
def _o2m_raise_on_missing_reference(self, cr, f):
# TODO this check should be a method on fields.one2many.
other = self.pool.get(f._obj)
if other:
# TODO the condition could use fields_get_keys().
@ -3140,9 +3155,9 @@ class BaseModel(object):
if f._fields_id not in other._inherit_fields.keys():
raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
def _m2m_raise_or_create_relation(self, cr, f):
m2m_tbl, col1, col2 = f._sql_names(self)
self._make_ext_id(cr, EXT_ID_PREFIX_M2M_TABLE + m2m_tbl)
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
if not cr.dictfetchall():
if not self.pool.get(f._obj):
@ -3150,7 +3165,6 @@ class BaseModel(object):
dest_model = self.pool.get(f._obj)
ref = dest_model._table
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
# create foreign key references with ondelete=cascade, unless the targets are SQL views
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
if not cr.fetchall():
@ -3166,7 +3180,7 @@ class BaseModel(object):
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
def _add_sql_constraints(self, cr):
def _add_sql_constraints(self, cr, module):
"""
Modify this model's database table constraints so they match the one in
@ -3179,9 +3193,9 @@ class BaseModel(object):
for (key, con, _) in self._sql_constraints:
conname = '%s_%s' % (self._table, key)
self._make_ext_id(cr, EXT_ID_PREFIX_CONSTRAINT + conname)
cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
existing_constraints = cr.dictfetchall()
sql_actions = {
'drop': {
'execute': False,
@ -3734,7 +3748,7 @@ class BaseModel(object):
wf_service = netsvc.LocalService("workflow")
for oid in ids:
wf_service.trg_delete(uid, self._name, oid, cr)
self.check_access_rule(cr, uid, ids, 'unlink', context=context)
pool_model_data = self.pool.get('ir.model.data')
@ -4354,7 +4368,7 @@ class BaseModel(object):
for v in value:
if v not in val:
continue
if self._columns[v]._type in ('many2one', 'one2one'):
if self._columns[v]._type == 'many2one':
try:
value[v] = value[v][0]
except:
@ -4376,7 +4390,7 @@ class BaseModel(object):
if f in field_dict[r]:
result.pop(r)
for id, value in result.items():
if self._columns[f]._type in ('many2one', 'one2one'):
if self._columns[f]._type == 'many2one':
try:
value = value[0]
except:
@ -4653,7 +4667,7 @@ class BaseModel(object):
data[f] = data[f] and data[f][0]
except:
pass
elif ftype in ('one2many', 'one2one'):
elif ftype == 'one2many':
res = []
rel = self.pool.get(fields[f]['relation'])
if data[f]:
@ -4704,7 +4718,7 @@ class BaseModel(object):
translation_records = []
for field_name, field_def in fields.items():
# we must recursively copy the translations for o2o and o2m
if field_def['type'] in ('one2one', 'one2many'):
if field_def['type'] == 'one2many':
target_obj = self.pool.get(field_def['relation'])
old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
# here we rely on the order of the ids to match the translations

View File

@ -299,7 +299,7 @@ class rml_parse(object):
parse_format = DEFAULT_SERVER_DATETIME_FORMAT
if isinstance(value, basestring):
# FIXME: the trimming is probably unreliable if format includes day/month names
# and those would need to be translated anyway.
# and those would need to be translated anyway.
date = datetime.strptime(value[:get_date_length(parse_format)], parse_format)
elif isinstance(value, time.struct_time):
date = datetime(*value[:6])
@ -321,7 +321,7 @@ class rml_parse(object):
return res
def display_address(self, address_browse_record):
return self.pool.get('res.partner.address')._display_address(self.cr, self.uid, address_browse_record)
return self.pool.get('res.partner')._display_address(self.cr, self.uid, address_browse_record)
def repeatIn(self, lst, name,nodes_parent=False):
ret_lst = []

View File

@ -173,6 +173,8 @@ class db(netsvc.ExportService):
raise Exception, e
def exp_drop(self, db_name):
if not self.exp_db_exist(db_name):
return False
openerp.modules.registry.RegistryManager.delete(db_name)
sql_db.close_db(db_name)
@ -180,6 +182,17 @@ class db(netsvc.ExportService):
cr = db.cursor()
cr.autocommit(True) # avoid transaction block
try:
# Try to terminate all other connections that might prevent
# dropping the database
try:
cr.execute("""SELECT pg_terminate_backend(procpid)
FROM pg_stat_activity
WHERE datname = %s AND
procpid != pg_backend_pid()""",
(db_name,))
except Exception:
pass
try:
cr.execute('DROP DATABASE "%s"' % db_name)
except Exception, e:

View File

@ -313,7 +313,8 @@ class graph(object):
self.order[level] = self.order[level]+1
for sec_end in self.transitions.get(node, []):
self.init_order(sec_end, self.result[sec_end]['x'])
if node!=sec_end:
self.init_order(sec_end, self.result[sec_end]['x'])
def order_heuristic(self):
@ -438,33 +439,27 @@ class graph(object):
l.reverse()
no = len(l)
if no%2==0:
first_half = l[no/2:]
factor = 1
else:
first_half = l[no/2+1:]
factor = 0
rest = no%2
first_half = l[no/2+rest:]
last_half = l[:no/2]
i=1
for child in first_half:
self.result[child]['y'] = mid_pos - (i - (factor * 0.5))
i += 1
for i, child in enumerate(first_half):
self.result[child]['y'] = mid_pos - (i+1 - (0 if rest else 0.5))
if self.transitions.get(child, False):
if last:
self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
last = self.tree_order(child, last)
if no%2:
if rest:
mid_node = l[no/2]
self.result[mid_node]['y'] = mid_pos
if self.transitions.get((mid_node), False):
if last:
self.result[mid_node]['y'] = last + len(self.transitions[mid_node])/2 + 1
last = self.tree_order(mid_node)
if node!=mid_node:
last = self.tree_order(mid_node)
else:
if last:
self.result[mid_node]['y'] = last + 1
@ -474,13 +469,14 @@ class graph(object):
i=1
last_child = None
for child in last_half:
self.result[child]['y'] = mid_pos + (i - (factor * 0.5))
self.result[child]['y'] = mid_pos + (i - (0 if rest else 0.5))
last_child = child
i += 1
if self.transitions.get(child, False):
if last:
self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
last = self.tree_order(child, last)
if node!=child:
last = self.tree_order(child, last)
if last_child:
last = self.result[last_child]['y']

View File

@ -86,8 +86,8 @@ class ReceiverEmail2Event(object):
def get_partners(self, headers, msg):
alladdresses = self.get_addresses(headers, msg)
address_ids = self.rpc(('res.partner.address', 'search', [('email', 'in', alladdresses)]))
addresses = self.rpc(('res.partner.address', 'read', address_ids))
address_ids = self.rpc(('res.partner', 'search', [('email', 'in', alladdresses)]))
addresses = self.rpc(('res.partner', 'read', address_ids))
return [x['partner_id'][0] for x in addresses]
def __call__(self, request):