[MERGE] from trunk
bzr revid: rco@openerp.com-20120403082743-7lf3rb6h0ff95w7f
|
@ -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.
|
|
@ -6,3 +6,11 @@ OpenERP Server
|
|||
:maxdepth: 1
|
||||
|
||||
test-framework
|
||||
|
||||
New feature merges
|
||||
++++++++++++++++++
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api/user_img_specs
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)"),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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 user’s 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:
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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-->
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
After Width: | Height: | Size: 4.0 KiB |
|
@ -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
|
||||
|
|
|
@ -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')]}">
|
||||
|
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -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>
|
||||
|
|
|
@ -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() < 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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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-->
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
-->
|
||||
|
|
|
@ -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 & 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 & Purchases">
|
||||
<page string="Sales & 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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
|
@ -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
|
||||
|
|
|
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.3 KiB |
|
@ -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."
|
||||
|
||||
|
|
|
@ -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.')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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):
|
||||
|
|