summaryrefslogtreecommitdiff
path: root/addons/mail
diff options
context:
space:
mode:
authorAntonin Bourguignon <abo@openerp.com>2012-10-30 16:46:47 +0100
committerAntonin Bourguignon <abo@openerp.com>2012-10-30 16:46:47 +0100
commit5be9d20a0bb33cc7d85bf26ade2628f8affbbdfd (patch)
tree10cfc4c9f9de428aede7ad533222d5261730b5c4 /addons/mail
parent614768508725c766e37702ff0fa3b47c411b680d (diff)
parent87d3caa50f8598eda05b1638edf46491ef01d27a (diff)
[MERGE] trunk
bzr revid: abo@openerp.com-20121030154647-6acbljvitcmphriz
Diffstat (limited to 'addons/mail')
-rw-r--r--addons/mail/__openerp__.py1
-rw-r--r--addons/mail/data/mail_demo.xml13
-rw-r--r--addons/mail/i18n/ar.po4
-rw-r--r--addons/mail/i18n/bg.po4
-rw-r--r--addons/mail/i18n/ca.po4
-rw-r--r--addons/mail/i18n/da.po4
-rw-r--r--addons/mail/i18n/de.po4
-rw-r--r--addons/mail/i18n/es.po4
-rw-r--r--addons/mail/i18n/es_CR.po4
-rw-r--r--addons/mail/i18n/es_PY.po4
-rw-r--r--addons/mail/i18n/fi.po4
-rw-r--r--addons/mail/i18n/fr.po6
-rw-r--r--addons/mail/i18n/gl.po4
-rw-r--r--addons/mail/i18n/hr.po4
-rw-r--r--addons/mail/i18n/hu.po4
-rw-r--r--addons/mail/i18n/it.po4
-rw-r--r--addons/mail/i18n/ja.po4
-rw-r--r--addons/mail/i18n/lt.po4
-rw-r--r--addons/mail/i18n/lv.po4
-rw-r--r--addons/mail/i18n/mn.po4
-rw-r--r--addons/mail/i18n/nl.po4
-rw-r--r--addons/mail/i18n/pl.po4
-rw-r--r--addons/mail/i18n/pt.po4
-rw-r--r--addons/mail/i18n/pt_BR.po4
-rw-r--r--addons/mail/i18n/ro.po4
-rw-r--r--addons/mail/i18n/ru.po4
-rw-r--r--addons/mail/i18n/sl.po4
-rw-r--r--addons/mail/i18n/sr@latin.po4
-rw-r--r--addons/mail/i18n/sv.po4
-rw-r--r--addons/mail/i18n/tr.po4
-rw-r--r--addons/mail/i18n/zh_CN.po4
-rw-r--r--addons/mail/mail_followers.py20
-rw-r--r--addons/mail/mail_group_view.xml3
-rw-r--r--addons/mail/mail_message.py541
-rw-r--r--addons/mail/mail_message_view.xml5
-rw-r--r--addons/mail/mail_thread.py53
-rw-r--r--addons/mail/mail_thread_view.xml2
-rw-r--r--addons/mail/res_partner_view.xml3
-rw-r--r--addons/mail/res_users.py5
-rw-r--r--addons/mail/security/ir.model.access.csv5
-rw-r--r--addons/mail/static/src/css/mail.css643
-rw-r--r--addons/mail/static/src/css/mail_compose_message.css166
-rw-r--r--addons/mail/static/src/js/mail.js1522
-rw-r--r--addons/mail/static/src/js/mail_followers.js55
-rw-r--r--addons/mail/static/src/xml/mail.xml262
-rw-r--r--addons/mail/static/src/xml/mail_followers.xml46
-rw-r--r--addons/mail/tests/test_mail.py322
-rw-r--r--addons/mail/tests/test_mail_access_rights.py39
-rw-r--r--addons/mail/wizard/mail_compose_message.py7
-rw-r--r--addons/mail/wizard/mail_compose_message_view.xml18
50 files changed, 2141 insertions, 1708 deletions
diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py
index 27565bc4102..0c32ba27d6f 100644
--- a/addons/mail/__openerp__.py
+++ b/addons/mail/__openerp__.py
@@ -84,7 +84,6 @@ Main Features
'css': [
'static/src/css/mail.css',
'static/src/css/mail_group.css',
- 'static/src/css/mail_compose_message.css',
],
'js': [
'static/lib/jquery.expander/jquery.expander.js',
diff --git a/addons/mail/data/mail_demo.xml b/addons/mail/data/mail_demo.xml
index 7bda32c002b..bda93fc485d 100644
--- a/addons/mail/data/mail_demo.xml
+++ b/addons/mail/data/mail_demo.xml
@@ -8,6 +8,7 @@
<field name="body">Your monthly meal vouchers arrived. You can get them at Christine's office.
This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</field>
<field name="type">comment</field>
+ <field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment0" model="mail.message">
@@ -16,6 +17,7 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
<field name="body"><![CDATA[Great.]]></field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
+ <field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment1" model="mail.message">
@@ -24,22 +26,25 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
<field name="body">Thanks, but where is Christine's office, if I may ask? (I'm new here)</field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
+ <field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment2" model="mail.message">
<field name="model">mail.group</field>
<field name="res_id" ref="group_all_employees"/>
- <field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
+ <field name="body">Building B3, second floor on the right :-)</field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
+ <field name="subtype_id" ref="mt_comment"/>
</record>
- <record id="message_blogpost0_comment1_2" model="mail.message">
+ <record id="message_blogpost0_comment3" model="mail.message">
<field name="model">mail.group</field>
<field name="res_id" ref="group_all_employees"/>
- <field name="body">Building B3, second floor on the right :-)</field>
- <field name="parent_id" ref="message_blogpost0_comment1"/>
+ <field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
+ <field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
+ <field name="subtype_id" ref="mt_comment"/>
</record>
</data>
diff --git a/addons/mail/i18n/ar.po b/addons/mail/i18n/ar.po
index 12768d69df7..2b86f7baaa7 100644
--- a/addons/mail/i18n/ar.po
+++ b/addons/mail/i18n/ar.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/bg.po b/addons/mail/i18n/bg.po
index 3a4cc768cc3..cfd1028ccb0 100644
--- a/addons/mail/i18n/bg.po
+++ b/addons/mail/i18n/bg.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/ca.po b/addons/mail/i18n/ca.po
index b42892cf9de..c99b70cd6a9 100644
--- a/addons/mail/i18n/ca.po
+++ b/addons/mail/i18n/ca.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/da.po b/addons/mail/i18n/da.po
index 677ef67bbca..1a9fdd72408 100644
--- a/addons/mail/i18n/da.po
+++ b/addons/mail/i18n/da.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/de.po b/addons/mail/i18n/de.po
index 9ceb5033a5f..ae2f3446e2d 100644
--- a/addons/mail/i18n/de.po
+++ b/addons/mail/i18n/de.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/es.po b/addons/mail/i18n/es.po
index 9a1eb786720..27f714bb972 100644
--- a/addons/mail/i18n/es.po
+++ b/addons/mail/i18n/es.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/es_CR.po b/addons/mail/i18n/es_CR.po
index a81d816fe78..91ecceef7ff 100644
--- a/addons/mail/i18n/es_CR.po
+++ b/addons/mail/i18n/es_CR.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
"Language: es\n"
#. module: mail
diff --git a/addons/mail/i18n/es_PY.po b/addons/mail/i18n/es_PY.po
index dcb4437a295..5b23b42ea04 100644
--- a/addons/mail/i18n/es_PY.po
+++ b/addons/mail/i18n/es_PY.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/fi.po b/addons/mail/i18n/fi.po
index a85366f0755..fec729cc86d 100644
--- a/addons/mail/i18n/fi.po
+++ b/addons/mail/i18n/fi.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/fr.po b/addons/mail/i18n/fr.po
index 47906d27a1c..446698d05f7 100644
--- a/addons/mail/i18n/fr.po
+++ b/addons/mail/i18n/fr.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
@@ -176,7 +176,7 @@ msgstr "Répondre"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Sent"
-msgstr "Envoyer"
+msgstr "Envoyé"
#. module: mail
#: help:mail.compose.message,subtype:0 help:mail.message,subtype:0
diff --git a/addons/mail/i18n/gl.po b/addons/mail/i18n/gl.po
index e7c8fb345be..9f7ba85353b 100644
--- a/addons/mail/i18n/gl.po
+++ b/addons/mail/i18n/gl.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/hr.po b/addons/mail/i18n/hr.po
index 4535063726e..a3a3f4f992f 100644
--- a/addons/mail/i18n/hr.po
+++ b/addons/mail/i18n/hr.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-29 05:00+0000\n"
-"X-Generator: Launchpad (build 16194)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/hu.po b/addons/mail/i18n/hu.po
index 86e15e751b3..000980eac99 100644
--- a/addons/mail/i18n/hu.po
+++ b/addons/mail/i18n/hu.po
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-27 04:59+0000\n"
-"X-Generator: Launchpad (build 16194)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/it.po b/addons/mail/i18n/it.po
index 008679dd40b..c21eeb8baf7 100644
--- a/addons/mail/i18n/it.po
+++ b/addons/mail/i18n/it.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/ja.po b/addons/mail/i18n/ja.po
index 0dd19cc7ecd..649ea0bb4d4 100644
--- a/addons/mail/i18n/ja.po
+++ b/addons/mail/i18n/ja.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/lt.po b/addons/mail/i18n/lt.po
index 0abb483ff49..276ca7a93af 100644
--- a/addons/mail/i18n/lt.po
+++ b/addons/mail/i18n/lt.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/lv.po b/addons/mail/i18n/lv.po
index 180ee3b25db..4ccffd6e47d 100644
--- a/addons/mail/i18n/lv.po
+++ b/addons/mail/i18n/lv.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/mn.po b/addons/mail/i18n/mn.po
index 0287464fb82..cace24a667d 100644
--- a/addons/mail/i18n/mn.po
+++ b/addons/mail/i18n/mn.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/nl.po b/addons/mail/i18n/nl.po
index 6538a93a163..2656f580004 100644
--- a/addons/mail/i18n/nl.po
+++ b/addons/mail/i18n/nl.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/pl.po b/addons/mail/i18n/pl.po
index 3b098c8f301..cb713dff12a 100644
--- a/addons/mail/i18n/pl.po
+++ b/addons/mail/i18n/pl.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/pt.po b/addons/mail/i18n/pt.po
index 9a7af60212e..4ccff2ade77 100644
--- a/addons/mail/i18n/pt.po
+++ b/addons/mail/i18n/pt.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/pt_BR.po b/addons/mail/i18n/pt_BR.po
index bfc15c06138..355e06e01ce 100644
--- a/addons/mail/i18n/pt_BR.po
+++ b/addons/mail/i18n/pt_BR.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/ro.po b/addons/mail/i18n/ro.po
index 278f47b5e53..5d347acb904 100644
--- a/addons/mail/i18n/ro.po
+++ b/addons/mail/i18n/ro.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/ru.po b/addons/mail/i18n/ru.po
index 61b01489c39..e43f9e6d2b8 100644
--- a/addons/mail/i18n/ru.po
+++ b/addons/mail/i18n/ru.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-27 04:59+0000\n"
-"X-Generator: Launchpad (build 16194)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/sl.po b/addons/mail/i18n/sl.po
index 908e5b4b704..2333a19aebb 100644
--- a/addons/mail/i18n/sl.po
+++ b/addons/mail/i18n/sl.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/sr@latin.po b/addons/mail/i18n/sr@latin.po
index 49b629bdc8a..6cbea6bb2bc 100644
--- a/addons/mail/i18n/sr@latin.po
+++ b/addons/mail/i18n/sr@latin.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/sv.po b/addons/mail/i18n/sv.po
index 75a76636ef4..2cc3534aa54 100644
--- a/addons/mail/i18n/sv.po
+++ b/addons/mail/i18n/sv.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/tr.po b/addons/mail/i18n/tr.po
index 20d01e461f9..6fa02d88241 100644
--- a/addons/mail/i18n/tr.po
+++ b/addons/mail/i18n/tr.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/i18n/zh_CN.po b/addons/mail/i18n/zh_CN.po
index ca14abd5eee..3fe80ca3076 100644
--- a/addons/mail/i18n/zh_CN.po
+++ b/addons/mail/i18n/zh_CN.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
-"X-Generator: Launchpad (build 16165)\n"
+"X-Launchpad-Export-Date: 2012-10-30 05:33+0000\n"
+"X-Generator: Launchpad (build 16206)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index 35603d2d720..e8cf2648f29 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -84,9 +84,16 @@ class mail_notification(osv.Model):
return False
def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
- """ TDE note: add a comment, verify method calls, because js seems obfuscated. """
+ """ Set a message and its child messages as (un)read for uid.
+
+ :param bool read: read / unread
+ """
+ # TDE note: use child_of or front-end send correct values ?
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
- notif_ids = self.search(cr, uid, [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)], context=context)
+ notif_ids = self.search(cr, uid, [
+ ('partner_id', '=', user_pid),
+ ('message_id', 'in', msg_ids)
+ ], context=context)
# all message have notifications: already set them as (un)read
if len(notif_ids) == len(msg_ids):
@@ -130,7 +137,8 @@ class mail_notification(osv.Model):
def _notify(self, cr, uid, msg_id, context=None):
""" Send by email the notification depending on the user preferences """
- context = context or {}
+ if context is None:
+ context = {}
# mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail'):
return True
@@ -140,9 +148,15 @@ class mail_notification(osv.Model):
if not notify_partner_ids:
return True
+ # add the context in the email
+ # TDE FIXME: commented, to be improved in a future branch
+ # quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
+
mail_mail = self.pool.get('mail.mail')
# add signature
body_html = msg.body
+ # if quote_context:
+ # body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
if signature:
body_html = tools.append_content_to_html(body_html, signature)
diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml
index 6268cd425b1..2028e8d1b18 100644
--- a/addons/mail/mail_group_view.xml
+++ b/addons/mail/mail_group_view.xml
@@ -82,9 +82,8 @@
</group>
</sheet>
<div class="oe_chatter">
- <field name="message_ids" widget="mail_thread"
- options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
</div>
</form>
</field>
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 8719d59bf04..bd0ade31b68 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -24,11 +24,17 @@ import tools
from email.header import decode_header
from openerp import SUPERUSER_ID
-from osv import osv, orm, fields
-from tools.translate import _
+from openerp.osv import osv, orm, fields
+from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
+try:
+ from mako.template import Template as MakoTemplate
+except ImportError:
+ _logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
+
+
""" Some tools for parsing / creating email fields """
def decode(text):
"""Returns unicode() string conversion of the the given encoded smtp header text"""
@@ -46,7 +52,7 @@ class mail_message(osv.Model):
_order = 'id desc'
_message_read_limit = 10
- _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read',
+ _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids']
_message_record_name_length = 18
_message_read_more_limit = 1024
@@ -57,17 +63,14 @@ class mail_message(osv.Model):
return name[:self._message_record_name_length] + '...'
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
- """ Return the related document name, using name_get. It is included in
- a try/except statement, because if uid cannot read the related
- document, he should see a void string instead of crashing. """
+ """ Return the related document name, using name_get. It is done using
+ SUPERUSER_ID, to be sure to have the record name correctly stored. """
+ # TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
- if not message['model'] or not message['res_id']:
+ if not message.get('model') or not message.get('res_id'):
continue
- try:
- result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, uid, [message['res_id']], context=context)[0][1])
- except (orm.except_orm, osv.except_osv):
- pass
+ result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1])
return result
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
@@ -78,10 +81,10 @@ class mail_message(osv.Model):
notif_ids = notif_obj.search(cr, uid, [
('partner_id', 'in', [partner_id]),
('message_id', 'in', ids),
- ('read', '=', False)
+ ('read', '=', False),
], context=context)
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
- res[notif.message_id.id] = not notif.read
+ res[notif.message_id.id] = True
return res
def _search_to_read(self, cr, uid, obj, name, domain, context=None):
@@ -115,16 +118,21 @@ class mail_message(osv.Model):
], 'Type',
help="Message type: email for email message, notification for system "\
"message, comment for other messages such as user replies"),
- 'author_id': fields.many2one('res.partner', 'Author', required=True),
- 'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
+ 'email_from': fields.char('From',
+ help="Email address of the sender. This field is set when no matching partner is found for incoming emails."),
+ 'author_id': fields.many2one('res.partner', 'Author',
+ help="Author of the message. If not set, email_from may hold an email address that did not match any partner."),
+ 'partner_ids': fields.many2many('res.partner', string='Recipients'),
+ 'notified_partner_ids': fields.many2many('res.partner', 'mail_notification',
+ 'message_id', 'partner_id', 'Recipients'),
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id', 'Attachments'),
'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
'model': fields.char('Related Document Model', size=128, select=1),
'res_id': fields.integer('Related Document ID', select=1),
- 'record_name': fields.function(_get_record_name, type='string',
- string='Message Record Name',
+ 'record_name': fields.function(_get_record_name, type='char',
+ store=True, string='Message Record Name',
help="Name get of the related document."),
'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
'subject': fields.char('Subject'),
@@ -198,19 +206,33 @@ class mail_message(osv.Model):
:param dict message: read result of a mail.message
"""
+ # TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP
+ is_author = False
+ if message['author_id']:
+ is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
+ author_id = message['author_id']
+ elif message['email_from']:
+ author_id = (0, message['email_from'])
+
has_voted = False
- if uid in message['vote_user_ids']:
+ if uid in message.get('vote_user_ids'):
has_voted = True
is_favorite = False
- if uid in message['favorite_user_ids']:
+ if uid in message.get('favorite_user_ids'):
is_favorite = True
+ is_private = True
+ if message.get('model') and message.get('res_id'):
+ is_private = False
+
try:
attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)]
except (orm.except_orm, osv.except_osv):
attachment_ids = []
+ # TDE note: should we send partner_ids ?
+ # TDE note: shouldn't we separated followers and other partners ? costly to compute maybe ,
try:
partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context)
except (orm.except_orm, osv.except_osv):
@@ -226,95 +248,106 @@ class mail_message(osv.Model):
'record_name': message['record_name'],
'subject': message['subject'],
'date': message['date'],
- 'author_id': message['author_id'],
- 'is_author': message['author_id'] and message['author_id'][0] == uid,
- # TDE note: is this useful ? to check
+ 'author_id': author_id,
+ 'is_author': is_author,
'partner_ids': partner_ids,
- 'parent_id': message['parent_id'] and message['parent_id'][0] or False,
- # TDE note: see with CHM about votes, how they are displayed (only number, or name_get ?)
- # vote: should only use number of votes
+ 'parent_id': False,
'vote_nb': len(message['vote_user_ids']),
'has_voted': has_voted,
- 'is_private': message['model'] and message['res_id'],
+ 'is_private': is_private,
'is_favorite': is_favorite,
'to_read': message['to_read'],
}
- def _message_read_expandable(self, cr, uid, message_list, read_messages,
- message_loaded_ids=[], domain=[], context=None, parent_id=False, limit=None):
- """ Create the expandable message for all parent message read
- this function is used by message_read
-
- :param list message_list: list of messages given by message_read to
- which we have to add expandables
+ def _message_read_add_expandables(self, cr, uid, message_list, read_messages,
+ thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None):
+ """ Create expandables for message_read, to load new messages.
+ 1. get the expandable for new threads
+ if display is flat (thread_level == 0):
+ fetch message_ids < min(already displayed ids), because we
+ want a flat display, ordered by id
+ else:
+ fetch message_ids that are not childs of already displayed
+ messages
+ 2. get the expandables for new messages inside threads if display
+ is not flat
+ for each thread header, search for its childs
+ for each hole in the child list based on message displayed,
+ create an expandable
+
+ :param list message_list:list of message structure for the Chatter
+ widget to which expandables are added
:param dict read_messages: dict [id]: read result of the messages to
easily have access to their values, given their ID
+ :return bool: True
"""
- # sort for group items / TDE: move to message_read
- # result = sorted(result, key=lambda k: k['id'])
- tree_not = []
- # expandable for not show message
+ def _get_expandable(domain, message_nb, parent_id, id, model):
+ return {
+ 'domain': domain,
+ 'nb_messages': message_nb,
+ 'type': 'expandable',
+ 'parent_id': parent_id,
+ 'id': id,
+ # TDE note: why do we need model sometimes, and sometimes not ???
+ 'model': model,
+ }
+
+ # all_not_loaded_ids = []
id_list = sorted(read_messages.keys())
+ if not id_list:
+ return message_list
+
+ # 1. get the expandable for new threads
+ if thread_level == 0:
+ exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))]
+ else:
+ exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)]
+ ids = self.search(cr, uid, exp_domain, context=context, limit=1)
+ if ids:
+ message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None))
+
+ # 2. get the expandables for new messages inside threads if display is not flat
+ if thread_level == 0:
+ return True
for message_id in id_list:
message = read_messages[message_id]
+ # message is not a thread header (has a parent_id)
+ # TDE note: parent_id is false is there is a parent we can not see -> ok
+ if message.get('parent_id'):
+ continue
+
# TDE note: check search is correctly implemented in mail.message
not_loaded_ids = self.search(cr, uid, [
- ('parent_id', '=', message['id']),
+ ('id', 'child_of', message['id']),
('id', 'not in', message_loaded_ids),
], context=context, limit=self._message_read_more_limit)
- # group childs not read
- id_min = None
- id_max = None
- nb = 0
+ if not not_loaded_ids:
+ continue
+ # all_not_loaded_ids += not_loaded_ids
+ # group childs not read
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
for not_loaded_id in not_loaded_ids:
if not read_messages.get(not_loaded_id):
nb += 1
- if id_min == None or id_min > not_loaded_id:
+ if id_min > not_loaded_id:
id_min = not_loaded_id
- if id_max == None or id_max < not_loaded_id:
+ if id_max < not_loaded_id:
id_max = not_loaded_id
- tree_not.append(not_loaded_id)
+ elif nb > 0:
+ exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
+ message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
else:
- if nb > 0:
- message_list.append({
- 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
- 'nb_messages': nb,
- 'type': 'expandable',
- 'parent_id': message_id,
- 'id': id_min,
- 'model': message['model']
- })
- id_min = None
- id_max = None
- nb = 0
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
if nb > 0:
- message_list.append({
- 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
- 'nb_messages': nb,
- 'type': 'expandable',
- 'parent_id': message_id,
- 'id': id_min,
- 'model': message['model'],
- })
-
- for msg_id in read_messages.keys() + tree_not:
- message_loaded_ids.append(msg_id)
-
- # expandable for limit max
- ids = self.search(cr, uid, domain + [('id', 'not in', message_loaded_ids)], context=context, limit=1)
- if len(ids) > 0:
- message_list.append({
- 'domain': domain,
- 'nb_messages': 0,
- 'type': 'expandable',
- 'parent_id': parent_id,
- 'id': -1,
- 'max_limit': True,
- })
+ exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
+ message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
- return message_list
+ # message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
+
+ return True
def _get_parent(self, cr, uid, message, context=None):
""" Tools method that tries to get the parent of a mail.message. If
@@ -331,57 +364,70 @@ class mail_message(osv.Model):
except (orm.except_orm, osv.except_osv):
return False
- def message_read(self, cr, uid, ids=False, domain=[], message_loaded_ids=[], context=None, parent_id=False, limit=None):
- """ Read messages from mail.message, and get back a structured tree
- of messages to be displayed as discussion threads. If IDs is set,
+ def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None):
+ """ Read messages from mail.message, and get back a list of structured
+ messages to be displayed as discussion threads. If IDs is set,
fetch these records. Otherwise use the domain to fetch messages.
- After having fetch messages, their parents & child will be added to obtain
- well formed threads.
-
- TDE note: update this comment after final method implementation
-
- :param domain: optional domain for searching ids
- :param limit: number of messages to fetch
- :param parent_id: if parent_id reached, stop searching for
- further parents
- :return list: list of trees of messages
+ After having fetch messages, their ancestors will be added to obtain
+ well formed threads, if uid has access to them.
+
+ After reading the messages, expandable messages are added in the
+ message list (see ``_message_read_add_expandables``). It consists
+ in messages holding the 'read more' data: number of messages to
+ read, domain to apply.
+
+ :param list ids: optional IDs to fetch
+ :param list domain: optional domain for searching ids if ids not set
+ :param list message_unload_ids: optional ids we do not want to fetch,
+ because i.e. they are already displayed somewhere
+ :param int parent_id: context of parent_id
+ - if parent_id reached when adding ancestors, stop going further
+ in the ancestor search
+ - if set in flat mode, ancestor_id is set to parent_id
+ :param int limit: number of messages to fetch, before adding the
+ ancestors and expandables
+ :return list: list of message structure for the Chatter widget
"""
- if message_loaded_ids:
- domain += [('id', 'not in', message_loaded_ids)]
+ # print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit
+ assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level
+ domain = domain if domain is not None else []
+ message_unload_ids = message_unload_ids if message_unload_ids is not None else []
+ if message_unload_ids:
+ domain += [('id', 'not in', message_unload_ids)]
limit = limit or self._message_read_limit
read_messages = {}
message_list = []
- # specific IDs given: fetch those ids and return directly the message list
- if ids:
- for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
- message_list.append(self._message_get_dict(cr, uid, message, context=context))
- message_list = sorted(message_list, key=lambda k: k['id'])
- return message_list
-
- # TDE FIXME: check access rights on search are implemented for mail.message
- # fetch messages according to the domain, add their parents if uid has access to
- ids = self.search(cr, uid, domain, context=context, limit=limit)
+ # no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
+ if ids is None:
+ ids = self.search(cr, uid, domain, context=context, limit=limit)
for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
- # if not in tree and not in message_loded list
- if not read_messages.get(message.get('id')) and message.get('id') not in message_loaded_ids:
- read_messages[message.get('id')] = message
+ message_id = message['id']
+
+ # if not in tree and not in message_loaded list
+ if not message_id in read_messages and not message_id in message_unload_ids:
+ read_messages[message_id] = message
message_list.append(self._message_get_dict(cr, uid, message, context=context))
- # get all parented message if the user have the access
+ # get the older ancestor the user can read, update its ancestor field
+ if not thread_level:
+ message_list[-1]['parent_id'] = parent_id
+ continue
parent = self._get_parent(cr, uid, message, context=context)
while parent and parent.get('id') != parent_id:
- if not read_messages.get(parent.get('id')) and parent.get('id') not in message_loaded_ids:
- read_messages[parent.get('id')] = parent
- message_list.append(self._message_get_dict(cr, uid, parent, context=context))
- parent = self._get_parent(cr, uid, parent, context=context)
+ message_list[-1]['parent_id'] = parent.get('id')
+ message = parent
+ parent = self._get_parent(cr, uid, message, context=context)
+ # if in thread: add its ancestor to the list of messages
+ if not message['id'] in read_messages and not message['id'] in message_unload_ids:
+ read_messages[message['id']] = message
+ message_list.append(self._message_get_dict(cr, uid, message, context=context))
# get the child expandable messages for the tree
message_list = sorted(message_list, key=lambda k: k['id'])
- message_list = self._message_read_expandable(cr, uid, message_list, read_messages,
- message_loaded_ids=message_loaded_ids, domain=domain, context=context, parent_id=parent_id, limit=limit)
+ self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level,
+ message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit)
- # message_list = sorted(message_list, key=lambda k: k['id'])
return message_list
# TDE Note: do we need this ?
@@ -394,7 +440,7 @@ class mail_message(osv.Model):
# return attachment_list
#------------------------------------------------------
- # Email api
+ # mail_message internals
#------------------------------------------------------
def init(self, cr):
@@ -402,23 +448,75 @@ class mail_message(osv.Model):
if not cr.fetchone():
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
+ def _search(self, cr, uid, args, offset=0, limit=None, order=None,
+ context=None, count=False, access_rights_uid=None):
+ """ Override that adds specific access rights of mail.message, to remove
+ ids uid could not see according to our custom rules. Please refer
+ to check_access_rule for more details about those rules.
+
+ After having received ids of a classic search, keep only:
+ - if author_id == pid, uid is the author, OR
+ - a notification (id, pid) exists, uid has been notified, OR
+ - uid have read access to the related document is model, res_id
+ - otherwise: remove the id
+ """
+ # Rules do not apply to administrator
+ # print '_search', uid, args
+ if uid == SUPERUSER_ID:
+ return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
+ context=context, count=count, access_rights_uid=access_rights_uid)
+ # Perform a super with count as False, to have the ids, not a counter
+ ids = super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
+ context=context, count=False, access_rights_uid=access_rights_uid)
+ if not ids and count:
+ return 0
+ elif not ids:
+ return ids
+
+ pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
+ author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
+ model_ids = {}
+
+ messages = super(mail_message, self).read(cr, uid, ids, ['author_id', 'model', 'res_id', 'notified_partner_ids'], context=context)
+ for message in messages:
+ if message.get('author_id') and message.get('author_id')[0] == pid:
+ author_ids.add(message.get('id'))
+ elif pid in message.get('notified_partner_ids'):
+ partner_ids.add(message.get('id'))
+ elif message.get('model') and message.get('res_id'):
+ model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id'))
+
+ model_access_obj = self.pool.get('ir.model.access')
+ for doc_model, doc_dict in model_ids.iteritems():
+ if not model_access_obj.check(cr, uid, doc_model, 'read', False):
+ continue
+ doc_ids = doc_dict.keys()
+ allowed_doc_ids = self.pool.get(doc_model).search(cr, uid, [('id', 'in', doc_ids)], context=context)
+ allowed_ids |= set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
+
+ final_ids = author_ids | partner_ids | allowed_ids
+ if count:
+ return len(final_ids)
+ else:
+ return list(final_ids)
+
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Access rules of mail.message:
- read: if
- - notification exist (I receive pushed message) OR
- - author_id = pid (I am the author) OR
- - I can read the related document if res_model, res_id
- - Otherwise: raise
+ - author_id == pid, uid is the author, OR
+ - mail_notification (id, pid) exists, uid has been notified, OR
+ - uid have read access to the related document if model, res_id
+ - otherwise: raise
- create: if
- - I am in the document message_follower_ids OR
- - I can write on the related document if res_model, res_id OR
- - I create a private message (no model, no res_id)
- - Otherwise: raise
+ - no model, no res_id, I create a private message
+ - pid in message_follower_ids if model, res_id OR
+ - uid have write access on the related document if model, res_id, OR
+ - otherwise: raise
- write: if
- - I can write on the related document if res_model, res_id
+ - uid has write access on the related document if model, res_id
- Otherwise: raise
- unlink: if
- - I can write on the related document if res_model, res_id
+ - uid has write access on the related document if model, res_id
- Otherwise: raise
"""
if uid == SUPERUSER_ID:
@@ -428,54 +526,50 @@ class mail_message(osv.Model):
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
# Read mail_message.ids to have their values
- model_record_ids = {}
message_values = dict.fromkeys(ids)
+ model_record_ids = {}
cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
for id, rmod, rid, author_id in cr.fetchall():
message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
if rmod:
- model_record_ids.setdefault(rmod, set()).add(rid)
+ model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
- # Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field
- if operation == 'read':
- not_obj = self.pool.get('mail.notification')
- not_ids = not_obj.search(cr, SUPERUSER_ID, [
- ('partner_id', '=', partner_id),
- ('message_id', 'in', ids),
- ], context=context)
- notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
- else:
- notified_ids = []
- # Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field
+ # Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
author_ids = [mid for mid, message in message_values.iteritems()
if message.get('author_id') and message.get('author_id') == partner_id]
- # Create: Check messages you create that are private messages -> ir.rule ?
elif operation == 'create':
author_ids = [mid for mid, message in message_values.iteritems()
if not message.get('model') and not message.get('res_id')]
else:
author_ids = []
- # Create: Check message_follower_ids
- if operation == 'create':
- doc_follower_ids = []
- for model, mids in model_record_ids.items():
+ # Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
+ if operation == 'read':
+ not_obj = self.pool.get('mail.notification')
+ not_ids = not_obj.search(cr, SUPERUSER_ID, [
+ ('partner_id', '=', partner_id),
+ ('message_id', 'in', ids),
+ ], context=context)
+ notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
+ elif operation == 'create':
+ notified_ids = []
+ for doc_model, doc_dict in model_record_ids.items():
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
- ('res_model', '=', model),
- ('res_id', 'in', list(mids)),
+ ('res_model', '=', doc_model),
+ ('res_id', 'in', list(doc_dict.keys())),
('partner_id', '=', partner_id),
], context=context)
fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
- doc_follower_ids += [mid for mid, message in message_values.iteritems()
- if message.get('res_model') == model and message.get('res_id') in fol_mids]
+ notified_ids += [mid for mid, message in message_values.iteritems()
+ if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
else:
- doc_follower_ids = []
+ notified_ids = []
# Calculate remaining ids, and related model/res_ids
model_record_ids = {}
- other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids))
+ other_ids = set(ids).difference(set(author_ids), set(notified_ids))
for id in other_ids:
if message_values[id]['res_model']:
model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
@@ -495,7 +589,7 @@ class mail_message(osv.Model):
if message.get('res_model') == model and message.get('res_id') in mids]
# Calculate remaining ids: if not void, raise an error
- other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids))
+ other_ids = other_ids - set(document_related_ids)
if not other_ids:
return
raise orm.except_orm(_('Access Denied'),
@@ -529,50 +623,123 @@ class mail_message(osv.Model):
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
return super(mail_message, self).unlink(cr, uid, ids, context=context)
- def _notify_followers(self, cr, uid, newid, message, context=None):
- """ Add the related record followers to the destination partner_ids.
+ def copy(self, cr, uid, id, default=None, context=None):
+ """ Overridden to avoid duplicating fields that are unique to each email """
+ if default is None:
+ default = {}
+ default.update(message_id=False, headers=False)
+ return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
+
+ #------------------------------------------------------
+ # Messaging API
+ #------------------------------------------------------
+
+ # TDE note: this code is not used currently, will be improved in a future merge, when quoted context
+ # will be added to email send for notifications. Currently only WIP.
+ MAIL_TEMPLATE = """<div>
+ % if message:
+ ${display_message(message)}
+ % endif
+ % for ctx_msg in context_messages:
+ ${display_message(ctx_msg)}
+ % endfor
+ % if add_expandable:
+ ${display_expandable()}
+ % endif
+ ${display_message(header_message)}
+ </div>
+
+ <%def name="display_message(message)">
+ <div>
+ Subject: ${message.subject}<br />
+ Body: ${message.body}
+ </div>
+ </%def>
+
+ <%def name="display_expandable()">
+ <div>This is an expandable.</div>
+ </%def>
+ """
+
+ def message_quote_context(self, cr, uid, id, context=None, limit=3, add_original=False):
"""
- partners_to_notify = set([])
- # message has no subtype_id: pure log message -> no partners, no one notified
- if not message.subtype_id:
- message.write({'partner_ids': [5]})
- return True
- # all partner_ids of the mail.message have to be notified
- if message.partner_ids:
- partners_to_notify |= set(partner.id for partner in message.partner_ids)
- # all followers of the mail.message document have to be added as partners and notified
- if message.model and message.res_id:
- fol_obj = self.pool.get("mail.followers")
- fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
- fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
- extra_notified = set(fol.partner_id.id for fol in fol_objs)
- missing_notified = extra_notified - partners_to_notify
- if missing_notified:
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
+ 1. message.parent_id = False: new thread, no quote_context
+ 2. get the lasts messages in the thread before message
+ 3. get the message header
+ 4. add an expandable between them
+
+ :param dict quote_context: options for quoting
+ :return string: html quote
+ """
+ add_expandable = False
+
+ message = self.browse(cr, uid, id, context=context)
+ if not message.parent_id:
+ return ''
+ context_ids = self.search(cr, uid, [
+ ('parent_id', '=', message.parent_id.id),
+ ('id', '<', message.id),
+ ], limit=limit, context=context)
+
+ if len(context_ids) >= limit:
+ add_expandable = True
+ context_ids = context_ids[0:-1]
+
+ context_ids.append(message.parent_id.id)
+ context_messages = self.browse(cr, uid, context_ids, context=context)
+ header_message = context_messages.pop()
+
+ try:
+ if not add_original:
+ message = False
+ result = MakoTemplate(self.MAIL_TEMPLATE).render_unicode(message=message,
+ context_messages=context_messages,
+ header_message=header_message,
+ add_expandable=add_expandable,
+ # context kw would clash with mako internals
+ ctx=context,
+ format_exceptions=True)
+ result = result.strip()
+ return result
+ except Exception:
+ _logger.exception("failed to render mako template for quoting message")
+ return ''
+ return result
def _notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids if is not a private message.
Call mail_notification.notify to manage the email sending
"""
- message = self.browse(cr, uid, newid, context=context)
- if message.model and message.res_id:
- self._notify_followers(cr, uid, newid, message, context=context)
+ message = self.read(cr, uid, newid, ['model', 'res_id', 'author_id', 'subtype_id', 'partner_ids'], context=context)
- # add myself if I wrote on my wall, otherwise remove myself author
- if ((message.model == "res.partner" and message.res_id == message.author_id.id)):
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, message.author_id.id)]}, context=context)
- else:
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, message.author_id.id)]}, context=context)
+ partners_to_notify = set([])
+ # message has no subtype_id: pure log message -> no partners, no one notified
+ if not message.get('subtype_id'):
+ return True
+ # all partner_ids of the mail.message have to be notified
+ if message.get('partner_ids'):
+ partners_to_notify |= set(message.get('partner_ids'))
+ # all followers of the mail.message document have to be added as partners and notified
+ if message.get('model') and message.get('res_id'):
+ fol_obj = self.pool.get("mail.followers")
+ fol_ids = fol_obj.search(cr, uid, [
+ ('res_model', '=', message.get('model')),
+ ('res_id', '=', message.get('res_id')),
+ ('subtype_ids', 'in', message.get('subtype_id')[0])
+ ], context=context)
+ fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
+ partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
+ # when writing to a wall
+ if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
+ partners_to_notify |= set([message.get('author_id')[0]])
+ elif message.get('author_id'):
+ partners_to_notify = partners_to_notify - set([message.get('author_id')[0]])
+
+ if partners_to_notify:
+ self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p_id) for p_id in partners_to_notify]}, context=context)
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
- def copy(self, cr, uid, id, default=None, context=None):
- """Overridden to avoid duplicating fields that are unique to each email"""
- if default is None:
- default = {}
- default.update(message_id=False, headers=False)
- return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
-
#------------------------------------------------------
# Tools
#------------------------------------------------------
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index 4677beee511..93b35797fd5 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -29,6 +29,7 @@
<group>
<field name="subject"/>
<field name="author_id"/>
+ <field name="email_from"/>
<field name="date"/>
<field name="type"/>
<field name="subtype_id"/>
@@ -38,6 +39,7 @@
<field name="res_id"/>
<field name="parent_id"/>
<field name="partner_ids" widget="many2many_tags"/>
+ <field name="notified_partner_ids" widget="many2many_tags"/>
</group>
</group>
<field name="body"/>
@@ -63,6 +65,9 @@
<filter string="Comments"
name="comments" help="Comments"
domain="[('type', '=', 'comment')]"/>
+ <filter string="Has attachments"
+ name="attachments"
+ domain="[('attachment_ids', '!=', False)]"/>
<filter string="Notifications"
name="notifications" help="Notifications"
domain="[('type', '=', 'notification')]"/>
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 04da686b49d..e986f12b822 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -554,7 +554,11 @@ class mail_thread(osv.AbstractModel):
('file2', 'bytes')}
}
"""
- msg_dict = {}
+ msg_dict = {
+ 'type': 'email',
+ 'subtype': 'mail.mt_comment',
+ 'author_id': False,
+ }
if not isinstance(message, Message):
if isinstance(message, unicode):
# Warning: message_from_string doesn't always work correctly on unicode,
@@ -572,7 +576,7 @@ class mail_thread(osv.AbstractModel):
if 'Subject' in message:
msg_dict['subject'] = decode(message.get('Subject'))
- # Envelope fields not stored in mail.message but made available for message_new()
+ # Envelope fields not stored in mail.message but made available for message_new()
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
@@ -581,6 +585,8 @@ class mail_thread(osv.AbstractModel):
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg_dict['author_id'] = author_ids[0]
+ else:
+ msg_dict['email_from'] = message.get('from')
partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
msg_dict['partner_ids'] = partner_ids
@@ -670,6 +676,18 @@ class mail_thread(osv.AbstractModel):
if self._mail_flat_thread and not parent_id and thread_id:
message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
parent_id = message_ids and message_ids[0] or False
+ # we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
+ elif parent_id:
+ message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
+ # avoid loops when finding ancestors
+ processed_list = []
+ if message_ids:
+ _counter, _counter_max = 0, 200
+ message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
+ while (message.parent_id and message.parent_id.id not in processed_list):
+ processed_list.append(message.parent_id.id)
+ message = message.parent_id
+ parent_id = message.id
values = kwargs
values.update({
@@ -689,27 +707,28 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context)
- def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
- subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
- # TDE FIXME: body is plaintext: convert it into html
- # when writing on res.partner, without specific thread_id -> redirect to the user's partner
- if self._name == 'res.partner' and not thread_id:
- thread_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
-
- new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=type,
- subtype=subtype, parent_id=parent_id, context=context)
+ def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
+ """ Wrapper on message_post, used only in Chatter (JS). The purpose is
+ to handle attachments.
+ # TDE FIXME: body is plaintext: convert it into html
+ """
+ new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
+ subtype='mail.mt_comment', parent_id=parent_id, context=context)
- # Chatter: attachments linked to the document (not done JS-side), load the message
- if attachments:
+ # HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
+ if attachment_ids:
ir_attachment = self.pool.get('ir.attachment')
mail_message = self.pool.get('mail.message')
- attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [('res_model', '=', 'mail.message'), ('res_id', '=', 0), ('create_uid', '=', uid), ('id', 'in', attachments)], context=context)
- if attachment_ids:
+ filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
+ ('res_model', '=', 'mail.compose.message'),
+ ('res_id', '=', 0),
+ ('create_uid', '=', uid),
+ ('id', 'in', attachment_ids)], context=context)
+ if filtered_attachment_ids:
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': self._name, 'res_id': thread_id}, context=context)
mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context)
- new_message = self.pool.get('mail.message').message_read(cr, uid, [new_message_id], context=context)
- return new_message
+ return new_message_id
#------------------------------------------------------
# Followers API
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
index 55cc1de1ec0..07b24725b05 100644
--- a/addons/mail/mail_thread_view.xml
+++ b/addons/mail/mail_thread_view.xml
@@ -39,7 +39,7 @@
<!-- MENU -->
<!-- Top menu item -->
- <menuitem name="Mails"
+ <menuitem name="Emails"
id="mail.mail_feeds_main"
groups="base.group_user"
sequence="10"/>
diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml
index 2a8430926d5..a4aa56ae971 100644
--- a/addons/mail/res_partner_view.xml
+++ b/addons/mail/res_partner_view.xml
@@ -9,9 +9,8 @@
<field name="arch" type="xml">
<xpath expr="//sheet" position="after">
<div class="oe_chatter">
- <field name="message_ids" widget="mail_thread"
- options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
</div>
</xpath>
</field>
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 54b8a76703f..1b6388111ba 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -111,8 +111,7 @@ class res_users(osv.Model):
alias_pool.unlink(cr, uid, alias_ids, context=context)
return res
- def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
- subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
+ def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id.
@@ -124,7 +123,7 @@ class res_users(osv.Model):
thread_id = thread_id[0]
partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0]
return self.pool.get('res.partner').message_post_api(cr, uid, partner_id, body=body, subject=subject,
- type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
+ parent_id=parent_id, attachment_ids=attachment_ids, context=context)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index c609667c26e..52c6c605109 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -2,6 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0
+access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
@@ -12,6 +13,6 @@ access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
-access_mail_message_subtype,mail.message.subtype,model_mail_message_subtype,,1,1,1,1
-access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
+access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
+access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1 \ No newline at end of file
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index 50ce0e000a9..af3cc3ed359 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -1,439 +1,382 @@
-/* ------------------------------------------------------------ */
-/* Reset because of ugly display of end of August
-/* ------------------------------------------------------------ */
-.openerp .oe_mail_wall ul, .openerp .oe_mail_wall li {
- list-style-type: none;
- padding: 0;
- margin: 0;
+/* ------------ MAIL WIDGET --------------- */
+.openerp .oe_mail, .openerp .oe_mail *{
+ box-sizing: border-box;
}
-
-.openerp .oe_chatter ul, .openerp .oe_chatter li {
- list-style-type: none;
- padding: 0;
- margin: 0;
-}
-
-/* ------------------------------------------------------------ */
-/* Wall
-/* ------------------------------------------------------------ */
-
-.openerp div.oe_mail_wall {
- overflow: auto;
- padding: 0;
- background: white;
+.openerp .oe_mail {
+ display: block;
+ position: relative;
+ margin: 0px;
}
-
-.openerp div.oe_mail_wall div.oe_mail_wall_aside {
- margin-left: 565px;
- margin: 8px;
+.openerp .oe_mail .oe_thread{
+ margin-left: 32px;
}
-
-.openerp div.oe_mail_wall ul.oe_mail_wall_threads {
- float: left;
- width: 560px;
- margin: 8px;
- list-style-type: none;
+.openerp .oe_mail > .oe_thread{
+ margin-left: 0px;
}
-/* ------------------------------------------------------------ */
-/* Followers
-/* ------------------------------------------------------------ */
+/* ---------------- MESSAGES ------------------ */
-.openerp div.oe_mail_recthread_aside h4 {
- display: inline-block;
-}
-.openerp div.oe_mail_recthread_aside button {
+.openerp .oe_mail .oe_msg{
position: relative;
+ background: #F4F5FA;
+ border-radius: 2px;
+ margin-bottom: 2px;
+ min-height: 42px;
+ border: solid 1px rgba(0,0,0,0.03);
}
-.openerp div.oe_mail_recthread_aside label,
-.openerp div.oe_mail_recthread_aside input {
- cursor:pointer;
+.openerp .oe_mail .oe_msg .oe_msg_left{
+ position: absolute;
+ left:0; top: 0; bottom: 0; width: 40px;
+ overflow: hidden;
}
-
-/* Specific display of threads in the wall */
-/* ------------------------------------------------------------ */
-
-.openerp ul.oe_mail_wall_threads .oe_msg_content textarea.oe_mail_compose_textarea {
- width: 434px;
- height: 30px;
- padding: 4px;
+.openerp .oe_mail .oe_msg .oe_msg_icon{
+ width: 32px;
+ margin: 4px;
+ border-radius: 2px;
}
-
-.openerp li.oe_mail_wall_thread:first .oe_msg_notification {
- border-top: 0;
+.openerp .oe_mail .oe_msg .oe_msg_center{
+ position: relative;
+ display: block;
+ margin-left: 40px;
}
-
-.openerp div.oe_thread_placeholder img {
- width: 28px;
- height: 28px;
+.openerp .oe_mail .oe_msg .oe_msg_footer{
+ padding-left: 4px;
+ overflow: hidden;
+ opacity:0.8;
+ -webkit-transition: opacity 0.2s linear;
}
-
-.openerp div.oe_thread_placeholder div.oe_msg_content {
- width: 440px;
+.openerp .oe_mail .oe_msg .oe_msg_content{
+ display: block;
+ overflow: hidden;
+ padding: 4px;
+ padding-bottom:1px;
+}
+.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_title{
+ font-size: 13px;
+ margin-bottom: 0px;
+ margin-top: 2px;
}
+/* a) Indented Messages */
-/* ------------------------------------------------------------ */
-/* RecordThread
-/* ------------------------------------------------------------ */
-
-.openerp .oe_form div.oe_chatter {
- overflow: auto;
+.openerp .oe_mail .oe_msg_indented{
+ background: #FFF;
+ border: none;
+ margin-bottom:0px;
+ min-height:38px;
}
-
-.openerp .oe_mail_record_wall {
- margin: auto;
- width: 560px;
+.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_icon{
+ width:32px;
+ margin:2px;
+ border-radius:2px;
}
-
-.openerp .oe_mail_record_wall > .oe_mail_wall_threads {
- float: left;
+.openerp .oe_mail .oe_msg .oe_subtle{
+ color: #B7B7D5;
}
-
-.openerp div.oe_mail_recthread_aside {
- float: right;
- width: 250px;
+.openerp .oe_mail .oe_msg_indented .oe_msg_center{
+ margin-left:34px;
}
-
-.openerp div.oe_mail_recthread_actions {
- margin-bottom: 8px;
+.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_content{
+ padding-top:2px;
}
-
-.openerp div.oe_mail_recthread_actions button {
- width: 120px;
+/* b) Votes (likes) */
+.openerp .oe_mail .oe_mail_vote_count{
+ display: inline;
+ position: relative;
+ background: #7C7BAD;
+ color: white;
+ text-shadow: none;
+ border-radius: 3px;
+ margin: 0px;
+ padding-left: 3px;
+ padding-right: 18px;
+ margin-right: 3px;
}
-
-.openerp .oe_mail_recthread_aside .oe_follower.oe_follow {
- color: white;
- background-color: #8a89ba;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4));
- background-image: -webkit-linear-gradient(top, #8a89ba, #807fb4);
- background-image: -moz-linear-gradient(top, #8a89ba, #807fb4);
- background-image: -ms-linear-gradient(top, #8a89ba, #807fb4);
- background-image: -o-linear-gradient(top, #8a89ba, #807fb4);
- background-image: linear-gradient(to bottom, #8a89ba, #807fb4);
-}
-.openerp .oe_mail_recthread_aside .oe_follower.oe_following {
- color: white;
- background-color: #dc5f59;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630));
- background-image: -webkit-linear-gradient(top, #dc5f59, #b33630);
- background-image: -moz-linear-gradient(top, #dc5f59, #b33630);
- background-image: -ms-linear-gradient(top, #dc5f59, #b33630);
- background-image: -o-linear-gradient(top, #dc5f59, #b33630);
- background-image: linear-gradient(to bottom, #dc5f59, #b33630);
+.openerp .oe_mail .oe_mail_vote_count .oe_e{
+ position: absolute;
+ bottom: 1px;
+ right: 2px;
+ font-size: 26px;
}
+/* c) Message action icons */
-.openerp .oe_mail_recthread_aside .oe_follower span {
+.openerp .oe_mail .oe_msg.oe_msg_unread .oe_unread{
display:none;
}
-.openerp .oe_mail_recthread_aside .oe_following span.oe_following,
-.openerp .oe_mail_recthread_aside .oe_notfollow span.oe_follow {
- display:block;
+.openerp .oe_mail .oe_msg.oe_msg_read .oe_read{
+ display:none;
}
-
-.openerp div.oe_mail_recthread_followers {
- margin-bottom: 8px;
+.openerp .oe_mail .oe_msg .oe_msg_icons{
+ float: right;
+ margin-top: 4px;
+ margin-right: 8px;
+ margin-left: 8px;
+ height: 24px;
+ -webkit-user-select: none;
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons span{
+ float:right;
+ width:24px;
+ height:24px;
+ line-height:24px;
+ text-align: center;
}
-
-
-/* ------------------------------------------------------------ */
-/* subtypes
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail_subtypes {
- display:inline-block;
- position: relative;
- z-index: 5;
+.openerp .oe_mail .oe_msg .oe_msg_icons a {
+ text-decoration: none;
+ color: #FFF;
+ text-shadow: 0px 1px #AAA,0px -1px #AAA, -1px 0px #AAA, 1px 0px #AAA, 0px 3px 3px rgba(0,0,0,0.1);
+ -webkit-transition: all 0.2s linear;
}
-.openerp .oe_mail_subtypes .oe_recthread_subtypes {
- background: #fff;
- padding: 2px;
- border: 1px solid #aaaaaa;
- border-top: 0px;
- position: absolute;
- z-index: 2;
+.openerp .oe_mail .oe_msg:hover .oe_msg_icons a{
+ opacity: 1;
+ -webkit-transition: all 0.1s linear;
}
-.openerp .oe_mail_subtypes.oe_mouseout .oe_recthread_subtypes {
- display: none;
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star:hover a{
+ color: #FFF6C0;
+ text-shadow: 0px 1px #FFA162,0px -1px #FFA162, -1px 0px #FFA162, 1px 0px #FFA162, 0px 3px 3px rgba(0,0,0,0.1);
}
-.openerp .oe_mail_subtypes.oe_mouseover .oe_recthread_subtypes {
- display: block;
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star.oe_starred a{
+ color: #FFE41F;
+ text-shadow: 0px 1px #DF6200,0px -1px #DF6200, -1px 0px #DF6200, 1px 0px #DF6200, 0px 3px 3px rgba(0,0,0,0.1);
}
-
-/* ------------------------------------------------------------ */
-/* Thread
-/* ------------------------------------------------------------ */
-
-.openerp div.oe_mail_thread_action {
- white-space: normal;
- padding: 8px;
- z-index:5;
- background: #fff;
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_reply:hover a{
+ color: #1fc0ff;
+ text-shadow: 0px 1px #184fc5,0px -1px #184fc5, -1px 0px #184fc5, 1px 0px #184fc5, 0px 3px 3px rgba(0,0,0,0.1);
}
-
-.openerp div.oe_mail_thread_action:after {
- content: "";
- display: block;
- clear: both;
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_read:hover a{
+ color: #bbbaff;
+ text-shadow: 0px 1px #7c7bad,0px -1px #7c7bad, -1px 0px #7c7bad, 1px 0px #7c7bad, 0px 3px 3px rgba(0,0,0,0.1);
}
-
-/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
-.openerp .oe_msg_content textarea.oe_mail_compose_textarea:focus,
-.openerp .oe_msg_content div.oe_mail_compose_message_body textarea:focus {
- outline: 0;
- border-color: rgba(82, 168, 236, 0.8);
- -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
- -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
- -box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_unread:hover a{
+ color: #c2ff00;
+ text-shadow: 0px 1px #009441,0px -1px #009441, -1px 0px #009441, 1px 0px #009441, 0px 3px 3px rgba(0,0,0,0.1);
}
-
-.openerp .oe_mail_vote_count,
-.openerp .oe_msg_vote{
- vertical-align: bottom;
+.openerp .oe_mail .oe_msg .oe_msg_content textarea{
+ width: 100%;
+ height: 32px;
+ margin: 0px;
+ padding: 0px;
+ resize: vertical;
+
+ padding: 4px;
}
-
-.openerp button.oe_mail_starbox{
- background: #ff0000;
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact, .openerp .oe_mail .oe_msg.oe_msg_expandable{
+ padding:4px;
+ min-height:0px;
}
-.openerp button.oe_mail_starbox.oe_stared{
- background: #00FF00;
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact textarea{
+ height: 24px;
+ width: 100%;
}
-.openerp div.oe_mail_thread_display {
- white-space: normal;
-}
+/* ---------------- MESSAGE QUICK COMPOSER --------------- */
-.openerp div.oe_thread_placeholder {
- margin-left: 66px;
+.openerp .oe_mail .oe_msg_composer .oe_msg_footer{
+ padding-right:4px;
+ padding-top: 2px;
+ padding-bottom:6px;
}
-
-.openerp li.oe_mail_thread_msg {
- width: 560px;
+.openerp .oe_mail .oe_msg_attachments.oe_hidden,
+.openerp .oe_mail .oe_msg_images.oe_hidden{
+ margin:0px;
+ border: none;
+ display: none;
}
-
-.openerp div.oe_thread_placeholder li.oe_mail_thread_msg:last-child {
- margin-bottom: 8px;
+.openerp .oe_mail .oe_msg_attachments{
+ margin-bottom: 4px;
+ margin-right: 0px;
+ font-size: 12px;
+ border-radius: 2px;
+ border: solid 1px rgba(124,123,173,0.14);
}
-
-.openerp div.oe_mail_thread_more {
- display: none;
- border-bottom: 1px solid #D2D9E7;
+.openerp .oe_mail .oe_msg_attachments .oe_attachment{
+ padding: 2px;
+ padding-left: 4px;
+ padding-right: 4px;
}
-
-.openerp li.oe_mail_thread_msg:after {
- content: "";
- display: block;
- clear: both;
+.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e{
+ font-size: 23px;
+ margin-top: -5px;
}
-
-.openerp li.oe_mail_thread_msg.oe_mail_read,
-.openerp li.oe_mail_thread_msg.oe_mail_read div {
- border-left: #F0F0F0;
+.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e:hover{
+ text-decoration: none;
}
-.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread,
-.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread div {
- background-color: #F6F6F6;
+.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(odd){
+ background:white;
}
-
-.openerp li.oe_mail_thread_msg.oe_mail_unread>div>ul>li.oe_unread,
-.openerp li.oe_mail_thread_msg.oe_mail_read>div>ul>li.oe_read {
- display: none;
+.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(even){
+ background: #F4F5FA;
}
-
-.openerp li.oe_mail_thread_msg > div:after {
- content: "";
+.openerp .oe_mail .oe_msg_images {
display: block;
- clear: both;
-}
-
-.openerp div.oe_mail_msg {
- padding: 0;
- margin: 0 0 4px 0;
}
-
-.openerp .oe_msg_notification,
-.openerp .oe_msg_expandable,
-.openerp .oe_msg_comment,
-.openerp .oe_msg_email {
- padding: 8px;
- background: white;
- position: relative;
+.openerp .oe_mail .oe_msg_footer button{
+ display: inline;
+ height: 24px;
+ font-size: 12px;
+ line-height: 12px;
+ vertical-align: middle;
}
-
-.openerp .oe_msg_notification:after,
-.openerp .oe_msg_comment:after,
-.openerp .oe_msg_email:after {
- content: "";
- display: block;
- clear: both;
+.openerp .oe_mail .oe_msg_footer button.oe_attach{
+ width: 24px;
+ overflow: hidden;
}
-
-.openerp div.oe_msg_content {
- float: left;
+.openerp .oe_mail .oe_msg_footer button.oe_attach .oe_e{
position: relative;
- width: 486px;
+ top: -1px;
+ left: -9px;
}
-
-.openerp div.oe_msg_content > li {
- float: left;
- margin-right: 3px;
+.openerp .oe_mail .oe_hidden_input_file, .openerp .oe_mail .oe_hidden_input_file form{
+ display:inline;
}
-
-.openerp .oe_msg_content:after {
- content: "";
- display: block;
- clear: both;
+.openerp .oe_mail .oe_msg_footer button.oe_full{
+ width:24px;
+ overflow:hidden;
+ float: right;
}
-
-.openerp .oe_chatter a {
- cursor: pointer;
+.openerp .oe_mail .oe_msg_footer button.oe_full .oe_e{
+ position: relative;
+ top: -1px;
+ left: -9px;
}
-
-.openerp img.oe_mail_icon {
- width: 50px;
- height: 50px;
+.openerp .oe_mail button.oe_attach, .openerp .oe_mail button.oe_full{
+ background: transparent;
+ color: #7C7BAD;
+ box-shadow: none;
+ border: none;
+ text-shadow: none;
}
-
-.openerp img.oe_mail_thumbnail {
- width: 28px;
+.openerp .oe_mail .oe_attach_label{
+ color: #7C7BAD;
+ margin-left: -3px;
+}
+.openerp .oe_mail .oe_msg_footer .oe_attachment_file .oe_form_binary_file{
+ display: inline-block;
+ margin-left: -47px;
height: 28px;
- margin: 4px;
+ width: 52px;
+ margin-top: -6px;
}
-
-.openerp img.oe_mail_frame {
- text-align: center;
- overflow: hidden;
- -moz-border-radius: 3px;
- -webkit-border-radius: 3px;
- -o-border-radius: 3px;
- -ms-border-radius: 3px;
- border-radius: 3px;
- -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- clip: rect(5px, 40px, 45px, 0px);
+.openerp .oe_mail .oe_mail_list_recipients{
+ font-size: 12px;
+ margin-top: 4px;
+ margin-bottom: 4px;
}
-.openerp .oe_mail_invisible {
- display: none;
-}
-
-/* ------------------------------------------------------------ */
-/* Messages layout
-/* ------------------------------------------------------------ */
+/* ---------------- HIDDEN MESSAGES ------------------ */
-.openerp .oe_mail_msg .oe_msg_title {
- margin: 0;
- font-size: 1.3em;
- font-weight: bold;
-}
-.openerp .oe_mail_msg .oe_msg_title a:link,
-.openerp .oe_mail_msg .oe_msg_title a:visited {
- color: #4C4C4C;
- text-decoration: none;
+.openerp .oe_mail .oe_msg_content.oe_msg_more_message{
+ text-align: right;
}
-
-.openerp .oe_mail_msg .oe_msg_body {
- margin-bottom: .5em;
- text-align: justify;
+.openerp .oe_mail .oe_msg_content.oe_msg_more_message .oe_separator{
+ height: 0;
+ border-bottom: dashed 1px #e6e6e6;
+ margin-left: -4px;
+ margin-right: 8px;
+ margin-top: 6px;
+ margin-bottom: -9px;
}
-
-.openerp .oe_mail_msg .oe_msg_body pre {
- font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
- margin: 0px;
- white-space: pre-wrap;
- display: inline;
+.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more {
+ background: white;
+ margin-right: 280px;
+ padding-left: 8px;
+ padding-right: 8px;
+ text-decoration: none;
+ color: #b4b4b4;
}
-
-/* Read more/less link */
-.openerp .oe_mail_msg span.oe_mail_reduce {
- position: absolute;
- right: 0;
+.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more:hover{
+ text-decoration: none;
}
-/* Dropdown menu */
+/* ---------------- FOLLOWERS ------------------ */
-.openerp .oe_mail ul.oe_mail_thread_display ul.oe_mail_thread_display {
+.openerp .oe_followers{
position: relative;
- border-left: 1px #DDD dashed;
+ display: inline-block;
+ padding-top: 5px;
+ width: 160px;
+ float: right;
}
-.openerp .oe_mail ul.oe_header {
- position: absolute;
- right: 3px;
- top: -6px;
- z-index: 10;
-}
+/* a) THE FOLLOW BUTTON */
-.openerp .oe_mail ul.oe_header a {
- text-decoration: none;
+.openerp .oe_followers button.oe_follower{
+ display: block;
+ text-align: center;
+ width:100%;
}
-
-.openerp .oe_mail ul.oe_header>li {
- display: inline-block;
- height: 20px;
- text-align: right;
+.openerp .oe_followers button.oe_follower.oe_following{
+ background-color: #3465A4;
+ background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
+ color: white;
}
-/* Message footer */
-.openerp .oe_mail_msg .oe_msg_footer {
- color: #888;
+.openerp .oe_followers button.oe_follower .oe_follow,
+.openerp .oe_followers button.oe_follower .oe_unfollow,
+.openerp .oe_followers button.oe_follower .oe_following{
+ display: none;
}
-.openerp .oe_mail_msg .oe_msg_footer li {
- float: left;
- margin-right: 3px;
+/* a.1) when following, show 'following' */
+.openerp .oe_followers button.oe_follower.oe_following .oe_following{
+ display: inline;
+}
+/* a.2) when following and hovering, show 'unfollow' */
+.openerp .oe_followers button.oe_follower.oe_following:hover .oe_following{
+ display: none;
}
-.openerp .oe_mail_msg .oe_msg_footer li:after {
- content: " · ";
+.openerp .oe_followers button.oe_follower.oe_following:hover .oe_unfollow{
+ display: inline;
}
-.openerp .oe_mail_msg .oe_msg_footer li:last-child:after {
- content: "";
+/* a.3) when not following show 'follow' */
+.openerp .oe_followers button.oe_follower.oe_notfollow .oe_follow{
+ display: inline;
}
-/* Attachments list */
-.openerp .oe_msg_content ul.oe_msg_attachments {
- width: 100%;
- margin: .5em 0 0 0;
- padding: .5em 0;
- list-style-position: inside;
+.openerp .oe_followers .oe_subtype_list{
+ margin-top: 4px;
}
-.openerp .oe_msg_content ul.oe_msg_attachments.oe_hidden {
- display: none;
+
+/* b) THE FOLLOWERS */
+.openerp .oe_followers .oe_follower_title{
+ display: inline;
}
-.openerp .oe_msg_content ul.oe_msg_attachments li {
- float: none;
- height: 20px;
- line-height: 20px;
- margin: 0;
- padding: 0;
- list-style-type: square;
+.openerp .oe_followers .oe_follower_title_box{
+ margin-top: 12px;
+ margin-bottom: 4px;
}
-.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process {
+.openerp .oe_followers .oe_invite{
float: right;
- width: 200px;
- height: 16px;
-}
-.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process div {
- float: left;
- width: 38px;
- height: 16px;
- margin-right: 2px;
- background: #66FF66;
-}
-.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process span {
- color: #aaaaaa;
- position: absolute;
+}
+.openerp .oe_followers .oe_partner {
+ height: 32px;
+ overflow: hidden;
+}
+.openerp .oe_followers .oe_partner img{
+ width: 32px;
+ margin-right:4px;
+ border-radius: 2px;
}
-/* ------------------------------------------------------------ */
-/* Topbar button
-/* ------------------------------------------------------------ */
+/* ---------------- MESSAGES BODY ------------------ */
+.openerp .oe_mail .oe_msg_content .oe_blockquote,
+.openerp .oe_mail .oe_msg_content blockquote {
+ padding: 4px;
+ border-radius: 2px;
+ border: solid 1px rgba(124,123,173,0.14);
+}
-.openerp .oe_topbar .oe_topbar_compose_full_email {
- float: right;
- margin: 3px 25px 0 0;
-} \ No newline at end of file
+/* ----------- FORM INTEGRATION ------------ */
+
+.openerp .oe_record_thread{
+ display: block;
+ margin-right: 180px;
+}
+
+/* ----------- INBOX INTEGRATION ----------- */
+
+.openerp .oe_mail_wall .oe_mail{
+ margin: 16px;
+ width: 720px;
+}
diff --git a/addons/mail/static/src/css/mail_compose_message.css b/addons/mail/static/src/css/mail_compose_message.css
deleted file mode 100644
index c706c55bcde..00000000000
--- a/addons/mail/static/src/css/mail_compose_message.css
+++ /dev/null
@@ -1,166 +0,0 @@
-/* ------------------------------ */
-/* Compose Message */
-/* ------------------------------ */
-
-.openerp .oe_msg_content .oe_mail_compose_message_footer {
- height: 24px;
-}
-
-.openerp .oe_msg_content .oe_mail_compose_message_footer button.oe_mail_compose_message_button_send {
- float: left;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea
-{
- display: none;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_header,
-.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_footer,
-{
- position: relative;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel {
- position: absolute;
- right: -8px;
- top: -8px;
-}
-.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel:first-of-type {
- display:none;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea button.oe_full {
- float: right;
- position: relative;
- right: -10px;
-}
-
-/* ------------------------------------------------------------ */
-/* mail.compose.message : list_recipients
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail .oe_mail_list_recipients {
- display: inline;
-}
-.openerp .oe_mail .oe_mail_list_recipients .oe_all_follower {
- color: blue;
-}
-.openerp .oe_mail .oe_mail_list_recipients .oe_partner_follower a {
- color: red;
-}
-.openerp .oe_mail .oe_mail_list_recipients .oe_hidden,
-.openerp .oe_mail .oe_mail_list_recipients .oe_more_hidden {
- display: none;
-}
-
-/* ------------------------------------------------------------ */
-/* mail.compose.message : attachment
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail .oe_attachment_file {
- display: inline-block;
-}
-
-.openerp .oe_mail .oe_attachment_file .oe_add {
- float: left;
- width: 24px;
- height: 24px;
- position: relative;
- z-index: 10;
- left: +2px;
- top: +7px;
- overflow: hidden;
-}
-
-/* attachment button: override of openerp values */
-.openerp .oe_mail .oe_attachment_file .oe_add button,
-.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
- position: absolute;
- bottom: +0px;
- left: +0px;
- height: 24px;
- width: 24px;
- margin: 0px;
- padding: 0px;
-}
-.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
- z-index:2;
- width: 300px;
- left: -100px;
- background: transparent;
- border: 0;
- color: transparent;
-}
-.openerp .oe_mail .oe_attachment_file .oe_add button span {
- position: relative;
- bottom: +4px;
- font-size: 30px;
-}
-
-.openerp .oe_mail .oe_msg_attachments input {
- visibility: hidden;
-}
-
-.openerp .oe_mail .oe_mail_compose_attachment_list {
- clear: both;
-}
-
-/* ------------------------------------------------------------ */
-/* mail.compose.message
-/* ------------------------------------------------------------ */
-
-
-/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
-.openerp .oe_mail.oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text,
-.openerp .oe_mail div.oe_mail_compose_message_body textarea.field_text {
- width: 100%;
- min-height: 120px;
- height: auto;
- padding: 4px;
- font-size: 12px;
- border: 1px solid #cccccc;
-}
-
-/* not top textarea */
-.openerp .oe_mail.oe_semantic_html_override .oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text {
- height: 60px;
-}
-
-/* form_view: delete white background */
-.openerp .oe_msg_content div.oe_formview {
- background-color: transparent;
-}
-
-.openerp .oe_msg_content div.oe_form_nosheet {
- margin: 0px;
-}
-
-.openerp .oe_msg_content table.oe_form_group {
- margin: 0px;
-}
-
-.openerp .oe_msg_content table.oe_form_field,
-.openerp .oe_msg_content div.oe_form_field {
- padding: 0px;
-}
-
-.openerp .oe_msg_content td.oe_form_group_cell {
- vertical-align: bottom;
-}
-
-/* subject: change width */
-.openerp .oe_msg_content .oe_form .oe_form_field input[type='text'] {
- width: 472px;
-}
-
-/* body_html: cleditor */
-.openerp .oe_msg_content div.cleditorMain {
- border: 1px solid #cccccc;
-}
-
-/* destination_partner_ids */
-.openerp .oe_msg_content div.text-core {
- height: 22px !important;
- width: 472px;
-} \ No newline at end of file
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 7501eabe104..9133e5de257 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -1,4 +1,4 @@
-openerp.mail = function(session) {
+openerp.mail = function (session) {
var _t = session.web._t,
_lt = session.web._lt;
@@ -16,15 +16,16 @@ openerp.mail = function(session) {
*/
session.web.FormView = session.web.FormView.extend({
- do_action: function(action) {
+ do_action: function (action) {
if (action.res_model == 'mail.compose.message') {
/* hack for stop context propagation of wrong value
* delete this hack when a global method to clean context is create
*/
var context_keys = ['default_template_id', 'default_composition_mode',
'default_use_template', 'default_partner_ids', 'default_model',
- 'default_res_id', 'default_content_subtype', 'active_id', 'lang',
- 'bin_raw', 'tz', 'active_model', 'edi_web_url_view', 'active_ids']
+ 'default_res_id', 'default_content_subtype', , 'default_subject',
+ 'default_body', 'active_id', 'lang', 'bin_raw', 'tz',
+ 'active_model', 'edi_web_url_view', 'active_ids']
for (var key in action.context) {
if (_.indexOf(context_keys, key) == -1) {
action.context[key] = null;
@@ -51,17 +52,18 @@ openerp.mail = function(session) {
mail.ChatterUtils = {
- /** Get an image in /web/binary/image?... */
- get_image: function(session, model, field, id) {
+ /* Get an image in /web/binary/image?... */
+ get_image: function (session, model, field, id) {
return session.prefix + '/web/binary/image?session_id=' + session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || '');
},
- /** Get the url of an attachment {'id': id} */
+ /* Get the url of an attachment {'id': id} */
get_attachment_url: function (session, attachment) {
return session.origin + '/web/binary/saveas?session_id=' + session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id'];
},
- /** Replaces some expressions
+ /**
+ * Replaces some expressions
* - :name - shortcut to an image
*/
do_replace_expressions: function (string) {
@@ -78,14 +80,36 @@ openerp.mail = function(session) {
return string;
},
- /* replace textarea text into html text
- * (add <p>, <a>)
- * TDE note : should not be here, but server-side I think ...
- */
- get_text2html: function(text){
+ /**
+ * Replaces textarea text into html text (add <p>, <a>)
+ * TDE note : should be done server-side, in Python -> use mail.compose.message ?
+ */
+ get_text2html: function (text) {
return text
.replace(/[\n\r]/g,'<br/>')
.replace(/((?:https?|ftp):\/\/[\S]+)/g,'<a href="$1">$1</a> ')
+ },
+
+ /* Returns the complete domain with "&"
+ * TDE note: please add some comments to explain how/why
+ */
+ expand_domain: function (domain) {
+ var new_domain = [];
+ var nb_and = -1;
+ // TDE note: smarted code maybe ?
+ for ( var k = domain.length-1; k >= 0 ; k-- ) {
+ if ( typeof domain[k] != 'array' && typeof domain[k] != 'object' ) {
+ nb_and -= 2;
+ continue;
+ }
+ nb_and += 1;
+ }
+
+ for (var k = 0; k < nb_and ; k++) {
+ domain.unshift('&');
+ }
+
+ return domain;
}
};
@@ -97,6 +121,8 @@ openerp.mail = function(session) {
*
* This widget handles the display of a form to compose a new message.
* This form is a mail.compose.message form_view.
+ * On first time : display a compact textarea that is not the compose form.
+ * When the user focuses the textarea, the compose message is instantiated.
*/
mail.ThreadComposeMessage = session.web.Widget.extend({
@@ -108,60 +134,50 @@ openerp.mail = function(session) {
* @param {Object} [context] context passed to the
* mail.compose.message DataSetSearch. Please refer to this model
* for more details about fields and default values.
- * @param {Boolean} [show_attachment_delete]
*/
- init: function (parent, options) {
+
+ init: function (parent, datasets, options) {
var self = this;
this._super(parent);
this.context = options.context || {};
-
- this.datasets = {
- 'attachment_ids' : [],
- 'id': options.datasets.id,
- 'model': options.datasets.model,
- 'res_model': options.datasets.res_model,
- 'is_private': options.datasets.is_private || false,
- 'partner_ids': options.datasets.partner_ids || []
- };
- this.options={};
- this.options.thread={};
- this.options.thread.show_header_compose = options.options.thread.show_header_compose;
- this.options.thread.display_on_thread = options.options.thread.display_on_thread;
- this.options.thread.show_attachment_delete = true;
- this.options.thread.show_attachment_link = true;
-
+ this.options = options.options;
+
+ this.show_compact_message = false;
+
+ // data of this compose message
+ this.attachment_ids = [];
+ this.id = datasets.id;
+ this.model = datasets.model;
+ this.res_model = datasets.res_model;
+ this.is_private = datasets.is_private || false;
+ this.partner_ids = datasets.partner_ids || [];
+ this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
+ this.thread_level = datasets.thread_level;
this.parent_thread= parent.messages!= undefined ? parent : false;
this.ds_attachment = new session.web.DataSetSearch(this, 'ir.attachment');
+ this.show_delete_attachment = true;
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
$(window).on(self.fileupload_id, self.on_attachment_loaded);
},
- start: function(){
+ start: function () {
this.display_attachments();
this.bind_events();
-
- //load avatar img
- var user_avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
- this.$('img.oe_mail_icon').attr('src', user_avatar);
},
/* upload the file on the server, add in the attachments list and reload display
*/
- display_attachments: function(){
- var self = this;
- var render = $(session.web.qweb.render('mail.thread.message.attachments', {'widget': self}));
- if(!this.list_attachment){
- this.$('.oe_mail_compose_attachment_list').replaceWith( render );
- } else {
- this.list_attachment.replaceWith( render );
- }
- this.list_attachment = this.$("ul.oe_msg_attachments");
-
+ display_attachments: function () {
+ this.$(".oe_msg_attachment_list").html(
+ session.web.qweb.render('mail.thread.message.attachments', {'widget': this}) );
// event: delete an attachment
- this.$el.on('click', '.oe_mail_attachment_delete', self.on_attachment_delete);
+ this.$(".oe_msg_attachment_list").on('click', '.oe_mail_attachment_delete', this.on_attachment_delete);
},
+
+ /* when a user click on the upload button, send file read on_attachment_loaded
+ */
on_attachment_change: function (event) {
event.stopPropagation();
var self = this;
@@ -172,25 +188,24 @@ openerp.mail = function(session) {
// if the files exits for this answer, delete the file before upload
var attachments=[];
- for(var i in this.datasets.attachment_ids){
- if((this.datasets.attachment_ids[i].filename || this.datasets.attachment_ids[i].name) == filename){
- if(this.datasets.attachment_ids[i].upload){
+ for (var i in this.attachment_ids) {
+ if ((this.attachment_ids[i].filename || this.attachment_ids[i].name) == filename) {
+ if (this.attachment_ids[i].upload) {
return false;
}
- this.ds_attachment.unlink([this.datasets.attachment_ids[i].id]);
+ this.ds_attachment.unlink([this.attachment_ids[i].id]);
} else {
- attachments.push(this.datasets.attachment_ids[i]);
+ attachments.push(this.attachment_ids[i]);
}
}
- this.datasets.attachment_ids = attachments;
+ this.attachment_ids = attachments;
// submit file
- //session.web.blockUI();
- self.$('form.oe_form_binary_form').submit();
+ this.$('form.oe_form_binary_form').submit();
this.$(".oe_attachment_file").hide();
- this.datasets.attachment_ids.push({
+ this.attachment_ids.push({
'id': 0,
'name': filename,
'filename': filename,
@@ -201,11 +216,12 @@ openerp.mail = function(session) {
}
},
+ /* when the file is uploaded
+ */
on_attachment_loaded: function (event, result) {
- //session.web.unblockUI();
- for(var i in this.datasets.attachment_ids){
- if(this.datasets.attachment_ids[i].filename == result.filename && this.datasets.attachment_ids[i].upload) {
- this.datasets.attachment_ids[i]={
+ for (var i in this.attachment_ids) {
+ if (this.attachment_ids[i].filename == result.filename && this.attachment_ids[i].upload) {
+ this.attachment_ids[i]={
'id': result.id,
'name': result.name,
'filename': result.filename,
@@ -219,6 +235,7 @@ openerp.mail = function(session) {
$input.after($input.clone(true)).remove();
this.$(".oe_attachment_file").show();
},
+
/* unlink the file on the server and reload display
*/
on_attachment_delete: function (event) {
@@ -226,61 +243,58 @@ openerp.mail = function(session) {
var attachment_id=$(event.target).data("id");
if (attachment_id) {
var attachments=[];
- for(var i in this.datasets.attachment_ids){
- if(attachment_id!=this.datasets.attachment_ids[i].id){
- attachments.push(this.datasets.attachment_ids[i]);
+ for (var i in this.attachment_ids) {
+ if (attachment_id!=this.attachment_ids[i].id) {
+ attachments.push(this.attachment_ids[i]);
}
else {
this.ds_attachment.unlink([attachment_id]);
}
}
- this.datasets.attachment_ids = attachments;
+ this.attachment_ids = attachments;
this.display_attachments();
}
},
- /* to avoid having unsorted file on the server.
- we will show the users files of the first message post
- TDE note: unnecessary call to server I think
- */
- // set_free_attachments: function(){
- // var self=this;
- // this.parent_thread.ds_message.call('user_free_attachment').then(function(attachments){
- // this.attachment_ids=[];
- // for(var i in attachments){
- // self.attachment_ids[i]={
- // 'id': attachments[i].id,
- // 'name': attachments[i].name,
- // 'filename': attachments[i].filename,
- // 'url': mail.ChatterUtils.get_attachment_url(self.session, attachments[i])
- // };
- // }
- // self.display_attachments();
- // });
- // },
-
- bind_events: function() {
+ bind_events: function () {
var self = this;
+ this.$('textarea.oe_compact').on('focus', _.bind( this.on_compose_expandable, this));
+
// set the function called when attachments are added
- this.$el.on('change', 'input.oe_form_binary_file', self.on_attachment_change );
- this.$el.on('click', 'a.oe_cancel', self.on_cancel );
- this.$el.on('click', 'button.oe_post', function(){self.on_message_post()} );
- this.$el.on('click', 'button.oe_full', function(){self.on_compose_fullmail()} );
+ this.$el.on('change', 'input.oe_form_binary_file', _.bind( this.on_attachment_change, this) );
+
+ this.$el.on('click', '.oe_cancel', _.bind( this.on_cancel, this) );
+ this.$el.on('click', '.oe_post', _.bind( this.on_message_post, this) );
+ this.$el.on('click', '.oe_full', _.bind( this.on_compose_fullmail, this, 'reply') );
+
+ /* stack for don't close the compose form if the user click on a button */
+ this.$el.on('mousedown', '.oe_msg_footer', _.bind( function () { this.stay_open = true; }, this));
+ this.$('textarea:not(.oe_compact):first').on('focus, mouseup, keydown', _.bind( function () { this.stay_open = false; }, this));
+ this.$('textarea:not(.oe_compact):first').autosize();
+
+ // auto close
+ this.$el.on('blur', 'textarea:not(.oe_compact):first', _.bind( this.on_compose_expandable, this));
},
- on_compose_fullmail: function(){
- var attachments=[];
- for(var i in this.datasets.attachment_ids){
- attachments.push(this.datasets.attachment_ids[i].id);
- }
- /* TDE note: I think this is not necessary, because
- * 1/ post on a document: followers added server-side in _notify
- * 2/ reply to a message: mail.compose.message should add the previous partners
- */
- var partner_ids=[];
- for(var i in this.datasets.partner_ids){
- partner_ids.push(this.datasets.partner_ids[i][0]);
+ on_compose_fullmail: function (default_composition_mode) {
+ if (default_composition_mode == 'reply') {
+ var context = {
+ 'default_composition_mode': default_composition_mode,
+ 'default_parent_id': this.id,
+ 'default_body': mail.ChatterUtils.get_text2html(this.$el ? (this.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
+ 'default_attachment_ids': this.attachment_ids,
+ };
+ } else {
+ var context = {
+ 'default_model': this.context.default_model,
+ 'default_res_id': this.context.default_res_id,
+ 'default_content_subtype': 'html',
+ 'default_composition_mode': default_composition_mode,
+ 'default_parent_id': this.id,
+ 'default_body': mail.ChatterUtils.get_text2html(this.$el ? (this.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
+ 'default_attachment_ids': this.attachment_ids,
+ };
}
var action = {
type: 'ir.actions.act_window',
@@ -290,70 +304,108 @@ openerp.mail = function(session) {
action_from: 'mail.ThreadComposeMessage',
views: [[false, 'form']],
target: 'new',
- context: {
- 'default_model': this.context.default_model,
- 'default_res_id': this.context.default_res_id,
- 'default_content_subtype': 'html',
- 'default_parent_id': this.context.default_parent_id,
- 'default_body': mail.ChatterUtils.get_text2html(this.$('textarea').val() || ''),
- 'default_attachment_ids': attachments,
- 'default_partner_ids': partner_ids
- },
+ context: context,
};
+
this.do_action(action);
+ this.on_cancel();
},
- on_cancel: function(event){
- if(event) event.stopPropagation();
- this.$('textarea').val("");
- this.$('input[data-id]').remove();
- //this.attachment_ids=[];
+ reinit: function() {
+ var $render = $( session.web.qweb.render('mail.compose_message', {'widget': this}) );
+
+ $render.insertAfter(this.$el.last());
+ this.$el.remove();
+ this.$el = $render;
+
this.display_attachments();
- if(!this.options.thread.show_header_compose || !this.options.thread.display_on_thread[0]){
- this.$el.hide();
- }
+ this.bind_events();
+ },
+
+ on_cancel: function (event) {
+ if (event) event.stopPropagation();
+ this.attachment_ids=[];
+ this.stay_open = false;
+ this.show_composer = false;
+ this.reinit();
},
/*post a message and fetch the message*/
- on_message_post: function (body) {
+ on_message_post: function (event) {
var self = this;
- if (! body) {
- var comment_node = this.$('textarea');
- var body = comment_node.val();
- comment_node.val('');
- }
+ var comment_node = this.$('textarea');
+ var body = comment_node.val();
+ comment_node.val('');
var attachments=[];
- for(var i in this.datasets.attachment_ids){
- if(this.datasets.attachment_ids[i].upload){
+ for (var i in this.attachment_ids) {
+ if (this.attachment_ids[i].upload) {
session.web.dialog($('<div>' + session.web.qweb.render('CrashManager.warning', {message: 'Please, wait while the file is uploading.'}) + '</div>'));
return false;
}
- attachments.push(this.datasets.attachment_ids[i].id);
+ attachments.push(this.attachment_ids[i].id);
}
- if(body.match(/\S+/)) {
+ if (body.match(/\S+/)) {
+ //session.web.blockUI();
this.parent_thread.ds_thread.call('message_post_api', [
this.context.default_res_id,
mail.ChatterUtils.get_text2html(body),
false,
- 'comment',
- 'mail.mt_comment',
this.context.default_parent_id,
attachments,
this.parent_thread.context
- ]).then(function(records){
- self.parent_thread.switch_new_message(records);
- self.datasets.attachment_ids=[];
- self.on_cancel();
+ ]).then(function (record) {
+ var thread = self.parent_thread;
+ // create object and attach to the thread object
+ thread.message_fetch(false, false, [record], function (arg, data) {
+ var message = thread.create_message_object( data[0] );
+ // insert the message on dom
+ thread.insert_message( message, self.$el );
+ if (thread.parent_message) {
+ self.$el.remove();
+ self.parent_thread.compose_message = null;
+ } else {
+ self.on_cancel();
+ }
+ });
+ //session.web.unblockUI();
});
return true;
}
},
+
+ /* convert the compact mode into the compose message
+ */
+ on_compose_expandable: function (event) {
+
+ if (!this.stay_open && (!this.show_composer || !this.$('textarea:not(.oe_compact)').val().match(/\S+/))) {
+ this.show_composer = !this.show_composer || this.stay_open;
+ this.reinit();
+ }
+ if (!this.stay_open && this.show_composer) {
+ this.$('textarea:not(.oe_compact):first').focus();
+ }
+ return true;
+ },
+
+ do_hide_compact: function () {
+ this.show_compact_message = false;
+ if (!this.show_composer) {
+ this.reinit();
+ }
+ },
+
+ do_show_compact: function () {
+ this.show_compact_message = true;
+ if (!this.show_composer) {
+ this.reinit();
+ }
+ }
});
- /**
+ /**
* ------------------------------------------------------------
* Thread Message Expandable Widget
* ------------------------------------------------------------
@@ -369,71 +421,81 @@ openerp.mail = function(session) {
mail.ThreadExpandable = session.web.Widget.extend({
template: 'mail.thread.expandable',
- init: function(parent, options) {
+ init: function (parent, datasets, context) {
this._super(parent);
- this.domain = options.domain || [];
+ this.domain = datasets.domain || [];
+ this.options = datasets.options;
this.context = _.extend({
default_model: 'mail.thread',
default_res_id: 0,
- default_parent_id: false }, options.context || {});
-
- this.datasets = {
- 'id' : options.datasets.id || -1,
- 'model' : options.datasets.model || false,
- 'parent_id' : options.datasets.parent_id || false,
- 'nb_messages' : options.datasets.nb_messages || 0,
- 'type' : 'expandable',
- 'max_limit' : options.datasets.max_limit || false,
- 'flag_used' : false,
- };
-
- // record options and data
- this.parent_thread= parent.messages!= undefined ? parent : options.options.thread._parents[0] ;
+ default_parent_id: false }, context || {});
+
+ // data of this expandable message
+ this.id = datasets.id || -1,
+ this.model = datasets.model || false,
+ this.parent_id = datasets.parent_id || false,
+ this.nb_messages = datasets.nb_messages || 0,
+ this.thread_level = datasets.thread_level || 0,
+ this.type = 'expandable',
+ this.max_limit = this.id < 0 || false,
+ this.flag_used = false,
+ this.parent_thread= parent.messages!= undefined ? parent : this.options._parents[0];
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
this.bind_events();
},
+ reinit: function () {
+ var $render = $(session.web.qweb.render('mail.thread.expandable', {'widget': this}));
+ this.$el.replaceWith( $render );
+ this.$el = $render;
+ this.bind_events();
+ },
+
/**
* Bind events in the widget. Each event is slightly described
* in the function. */
- bind_events: function() {
- var self = this;
- this.$el.on('click', 'a.oe_mail_fetch_more', self.on_expandable);
+ bind_events: function () {
+ this.$el.on('click', 'a.oe_msg_fetch_more', this.on_expandable);
},
- animated_destroy: function(options) {
+ animated_destroy: function (fadeTime) {
var self=this;
- //graphic effects
- if(options && options.fadeTime) {
- self.$el.fadeOut(options.fadeTime, function(){
- self.destroy();
- });
- } else {
+ this.$el.fadeOut(fadeTime, function () {
self.destroy();
- }
+ });
},
/*The selected thread and all childs (messages/thread) became read
* @param {object} mouse envent
*/
on_expandable: function (event) {
- if(event)event.stopPropagation();
- if(this.datasets.flag_used) {
+ if (event)event.stopPropagation();
+ if (this.flag_used) {
return false
}
- this.datasets.flag_used = true;
+ this.flag_used = true;
- this.animated_destroy({'fadeTime':300});
- this.parent_thread.message_fetch(false, this.domain, this.context);
+ this.animated_destroy(200);
+ this.parent_thread.message_fetch(this.domain, this.context);
return false;
},
+
+ /**
+ * call on_message_delete on his parent thread
+ */
+ destroy: function () {
+
+ this._super();
+ this.parent_thread.on_message_detroy(this);
+
+ }
});
- /**
+ /**
* ------------------------------------------------------------
* Thread Message Widget
* ------------------------------------------------------------
@@ -463,80 +525,56 @@ openerp.mail = function(session) {
* @param {Object} [options]
* @param {Object} [thread] read obout mail.Thread object
* @param {Object} [message]
- * @param {Number} [message_ids=null] ids for message_fetch
- * @param {Number} [message_data=null] already formatted message data,
- * for subthreads getting data from their parent
* @param {Number} [truncate_limit=250] number of character to
* display before having a "show more" link; note that the text
* will not be truncated if it does not have 110% of the parameter
* @param {Boolean} [show_record_name]
- * @param {Boolean} [show_dd_delete]
- * @param {Array [A,B]} [show_reply] display the reply button on the
- * message for thread level between A and B. -1 for no begin or no end.
- * @param {Array [A,B]} [show_read_unread] display the read/unread button on the
- * message for thread level between A and B. -1 for no begin or no end.
+ *... @param {int} [show_reply_button] number thread level to display the reply button
+ *... @param {int} [show_read_unread_button] number thread level to display the read/unread button
*/
- init: function(parent, options) {
+ init: function (parent, datasets, context) {
this._super(parent);
- // record datasets
- var param = options.datasets;
- this.datasets = _.extend({
- 'id' : -1,
- 'model' : false,
- 'parent_id': false,
- 'res_id' : false,
- 'type' : false,
- 'is_author' : false,
- 'is_private' : false,
- 'subject' : false,
- 'name' : false,
- 'record_name' : false,
- 'body' : false,
- 'vote_user_ids' :[],
- 'has_voted' : false,
- 'is_favorite' : false,
- 'thread_level' : 0,
- 'to_read' : true,
- 'author_id' : [],
- 'attachment_ids' : [],
- }, param || {});
- this.datasets._date = param.date;
-
// record domain and context
- this.domain = options.domain || [];
+ this.domain = datasets.domain || [];
this.context = _.extend({
default_model: 'mail.thread',
default_res_id: 0,
- default_parent_id: false }, options.context || {});
+ default_parent_id: false }, context || {});
// record options
- this.options={
- 'thread' : options.options.thread,
- 'message' : {
- 'message_ids': options.options.message.message_ids || null,
- 'message_data': options.options.message.message_data || null,
- 'show_record_name': options.options.message.show_record_name != undefined ? options.options.message.show_record_name: true,
- 'show_dd_delete': options.options.message.show_dd_delete || false,
- 'truncate_limit': options.options.message.truncate_limit || 250,
- 'show_reply': options.options.message.show_reply || [0,-1],
- 'show_read_unread': options.options.message.show_read_unread || [0,-1],
- }
- };
-
- this.datasets.show_reply = this.options.message.show_reply[0]>=0 &&
- this.options.message.show_reply[0]<=this.datasets.thread_level &&
- (this.options.message.show_reply[1]<0 || this.options.message.show_reply[1]>=this.datasets.thread_level);
-
- this.datasets.show_read_unread = this.options.message.show_read_unread[0]>=0 &&
- this.options.message.show_read_unread[0]<=this.datasets.thread_level &&
- (this.options.message.show_read_unread[1]<0 || this.options.message.show_read_unread[1]>=this.datasets.thread_level);
+ this.options = datasets.options;
+
+ // data of this message
+ this.id = datasets.id || -1,
+ this.model = datasets.model || false,
+ this.parent_id = datasets.parent_id || false,
+ this.res_id = datasets.res_id || false,
+ this.type = datasets.type || false,
+ this.is_author = datasets.is_author || false,
+ this.is_private = datasets.is_private || false,
+ this.subject = datasets.subject || false,
+ this.name = datasets.name || false,
+ this.record_name = datasets.record_name || false,
+ this.body = datasets.body || false,
+ this.vote_nb = datasets.vote_nb || 0,
+ this.has_voted = datasets.has_voted || false,
+ this.is_favorite = datasets.is_favorite || false,
+ this.thread_level = datasets.thread_level || 0,
+ this.to_read = datasets.to_read || false,
+ this.author_id = datasets.author_id || [],
+ this.attachment_ids = datasets.attachment_ids || [],
+ this._date = datasets.date;
+
+
+ this.show_reply_button = this.options.show_compose_message && this.options.show_reply_button > this.thread_level;
+ this.show_read_unread_button = this.options.show_read_unread_button > this.thread_level;
// record options and data
- this.parent_thread= parent.messages!= undefined ? parent : options.options.thread._parents[0];
+ this.parent_thread= parent.messages!= undefined ? parent : this.options._parents[0];
this.thread = false;
- if( this.datasets.id > 0 ) {
+ if ( this.id > 0 ) {
this.formating_data();
}
@@ -545,63 +583,93 @@ openerp.mail = function(session) {
this.ds_follow = new session.web.DataSetSearch(this, 'mail.followers');
},
- formating_data: function(){
+ /* Convert date, timerelative and avatar in displayable data. */
+ formating_data: function () {
//formating and add some fields for render
- this.datasets.date = session.web.format_value(this.datasets._date, {type:"datetime"});
- this.datasets.timerelative = $.timeago(this.datasets.date);
- if (this.datasets.type == 'email') {
- this.datasets.avatar = ('/mail/static/src/img/email_icon.png');
+ this.date = session.web.format_value(this._date, {type:"datetime"});
+ this.timerelative = $.timeago(this.date);
+ if (this.type == 'email') {
+ this.avatar = ('/mail/static/src/img/email_icon.png');
} else {
- this.datasets.avatar = mail.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', this.datasets.author_id[0]);
+ this.avatar = mail.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', this.author_id[0]);
}
- for (var l in this.datasets.attachment_ids) {
- var attach = this.datasets.attachment_ids[l];
+ for (var l in this.attachment_ids) {
+ var attach = this.attachment_ids[l];
attach['url'] = mail.ChatterUtils.get_attachment_url(this.session, attach);
+
+ if ((attach.filename || attach.name).match(/[.](jpg|jpg|gif|png|tif|svg)$/i)) {
+ attach.is_image = true;
+ attach['url'] = mail.ChatterUtils.get_image(this.session, 'ir.attachment', 'datas', attach.id);
+ }
}
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
this.expender();
- this.$el.hide().fadeIn(750);
+ this.$el.hide().fadeIn(750, function () {$(this).css('display', '');});
+ this.resize_img();
this.bind_events();
- this.create_thread();
+ if(this.thread_level < this.options.display_indented_thread) {
+ this.create_thread();
+ }
+ this.$('.oe_msg_attachments, .oe_msg_images').addClass("oe_hidden");
+ },
+
+ resize_img: function () {
+ var resize = function () {
+ var h = $(this).height();
+ var w = $(this).width();
+ if ( h > 100 || w >100 ) {
+ var ratio = 100 / (h > w ? h : w);
+ $(this).attr("width", parseInt( w*ratio )).attr("height", parseInt( h*ratio ));
+ }
+ };
+ this.$("img").load(resize).each(resize);
},
/**
* Bind events in the widget. Each event is slightly described
* in the function. */
- bind_events: function() {
+ bind_events: function () {
var self = this;
// event: click on 'Attachment(s)' in msg
- this.$('a.oe_msg_view_attachments:first').on('click', function (event) {
- self.$('.oe_msg_attachments:first').toggle();
+ this.$('.oe_mail_msg_view_attachments').on('click', function (event) {
+ var attach = self.$('.oe_msg_attachments:first, .oe_msg_images:first');
+ if ( self.$('.oe_msg_attachments:first').hasClass("oe_hidden") ) {
+ attach.removeClass("oe_hidden");
+ } else {
+ attach.addClass("oe_hidden");
+ }
+ self.resize_img();
});
// event: click on icone 'Read' in header
- this.$el.on('click', 'a.oe_read', this.on_message_read_unread);
+ this.$el.on('click', '.oe_read', this.on_message_read_unread);
// event: click on icone 'UnRead' in header
- this.$el.on('click', 'a.oe_unread', this.on_message_read_unread);
+ this.$el.on('click', '.oe_unread', this.on_message_read_unread);
// event: click on 'Delete' in msg side menu
- this.$el.on('click', 'a.oe_msg_delete', this.on_message_delete);
+ this.$el.on('click', '.oe_msg_delete', this.on_message_delete);
// event: click on 'Reply' in msg
- this.$el.on('click', 'a.oe_reply', this.on_message_reply);
+ this.$el.on('click', '.oe_reply', this.on_message_reply);
// event: click on 'Vote' button
- this.$el.on('click', 'button.oe_msg_vote', this.on_vote);
- // event: click on 'Star' button
- this.$el.on('click', 'button.oe_mail_starbox', this.on_star);
+ this.$el.on('click', '.oe_msg_vote', this.on_vote);
+ // event: click on 'starred/favorite' button
+ this.$el.on('click', '.oe_star', this.on_star);
},
- on_message_reply:function(event){
+ /* Call the on_compose_message on the thread of this message. */
+ on_message_reply:function (event) {
event.stopPropagation();
+ this.create_thread();
this.thread.on_compose_message();
return false;
},
- expender: function(){
- this.$('div.oe_msg_body:first').expander({
+ expender: function () {
+ this.$('.oe_msg_body:first').expander({
slicePoint: this.options.truncate_limit,
expandText: 'read more',
userCollapseText: '[^]',
@@ -611,52 +679,55 @@ openerp.mail = function(session) {
});
},
- create_thread: function(){
- var self=this;
- if(this.thread){
+ /**
+ * Instantiate the thread object of this message.
+ * Each message have only one thread.
+ */
+ create_thread: function () {
+ if (this.thread) {
return false;
}
-
/*create thread*/
- self.thread = new mail.Thread(self, {
- 'domain': self.domain,
+ this.thread = new mail.Thread(this, this, {
+ 'domain': this.domain,
'context':{
- 'default_model': self.datasets.model,
- 'default_res_id': self.datasets.res_id,
- 'default_parent_id': self.datasets.id
+ 'default_model': this.model,
+ 'default_res_id': this.res_id,
+ 'default_parent_id': this.id
},
- 'options': {
- 'thread' : self.options.thread,
- 'message' : self.options.message
- },
- 'datasets': self.datasets
+ 'options': this.options
}
);
/*insert thread in parent message*/
- self.thread.appendTo(self.$el.find('div.oe_thread_placeholder'));
+ this.thread.insertAfter(this.$el);
},
- animated_destroy: function(options) {
+ /**
+ * Fade out the message and his child thread.
+ * Then this object is destroyed.
+ */
+ animated_destroy: function (fadeTime) {
var self=this;
- //graphic effects
- if(options && options.fadeTime) {
- self.$el.fadeOut(options.fadeTime, function(){
- self.destroy();
- });
- } else {
- self.destroy();
+ this.$el.fadeOut(fadeTime, function () {
+ self.parent_thread.message_to_expandable(self);
+ });
+ if (this.thread) {
+ this.thread.$el.fadeOut(fadeTime);
}
},
+ /**
+ * Wait a confirmation for delete the message on the DB.
+ * Make an animate destroy
+ */
on_message_delete: function (event) {
event.stopPropagation();
if (! confirm(_t("Do you really want to delete this message?"))) { return false; }
- this.animated_destroy({fadeTime:250});
+ this.animated_destroy(150);
// delete this message and his childs
- var ids = [this.datasets.id].concat( this.get_child_ids() );
+ var ids = [this.id].concat( this.get_child_ids() );
this.ds_message.unlink(ids);
- this.animated_destroy();
return false;
},
@@ -664,117 +735,125 @@ openerp.mail = function(session) {
* @param {object} mouse envent
*/
on_message_read_unread: function (event) {
- // TDE note: code here seems complicated... just check that current message is read (value coming from server)
- // and send its opposite to set_message_read
event.stopPropagation();
- // if this message is read, all childs message display is read
- var ids = [this.datasets.id].concat( this.get_child_ids() );
- var read = $(event.srcElement).hasClass("oe_read");
- this.$el.removeClass("oe_mail_" + (read?"un":"") + "read").addClass("oe_mail_" + (read?"":"un") + "read");
+ var self=this;
- if( (read && this.options.thread.typeof_thread == 'inbox') ||
- (!read && this.options.thread.typeof_thread == 'archives')) {
- this.animated_destroy({fadeTime:250});
+ if ( (this.to_read && this.options.typeof_thread == 'inbox') ||
+ (!this.to_read && this.options.typeof_thread == 'archives')) {
+ this.animated_destroy(150);
}
- // TDE note: should have a context here
- this.ds_notification.call('set_message_read', [ids, read]);
+
+ // if this message is read, all childs message display is read
+ this.ds_notification.call('set_message_read', [ [this.id].concat( this.get_child_ids() ) , this.to_read, this.context]).pipe(function () {
+ self.$el.removeClass(self.to_read ? 'oe_msg_unread':'oe_msg_read').addClass(self.to_read ? 'oe_msg_read':'oe_msg_unread');
+ self.to_read = !self.to_read;
+ });
return false;
},
- /** browse message
+ /**
+ * search a message in all thread and child thread.
+ * This method return an object message.
* @param {object}{int} option.id
* @param {object}{string} option.model
* @param {object}{boolean} option._go_thread_wall
* private for check the top thread
* @return thread object
*/
- browse_message: function(options){
+ browse_message: function (options) {
// goto the wall thread for launch browse
- if(!options._go_thread_wall) {
+ if (!options._go_thread_wall) {
options._go_thread_wall = true;
- for(var i in this.options.thread._parents[0].messages){
- var res=this.options.thread._parents[0].messages[i].browse_message(options);
- if(res) return res;
+ for (var i in this.options._parents[0].messages) {
+ var res=this.options._parents[0].messages[i].browse_message(options);
+ if (res) return res;
}
}
- if(this.datasets.id==options.id)
+ if (this.id==options.id)
return this;
- for(var i in this.thread.messages){
- if(this.thread.messages[i].thread){
+ for (var i in this.thread.messages) {
+ if (this.thread.messages[i].thread) {
var res=this.thread.messages[i].browse_message(options);
- if(res) return res;
+ if (res) return res;
}
}
return false;
},
- /* get all child message/thread id linked
+ /* get all child message id linked.
+ * @return array of id
*/
- get_child_ids: function(){
+ get_child_ids: function () {
var res=[]
- if(arguments[0]) res.push(this.datasets.id);
- if(this.thread){
+ if (arguments[0]) res.push(this.id);
+ if (this.thread) {
res = res.concat( this.thread.get_child_ids(true) );
}
return res;
},
+ /**
+ * add or remove a vote for a message and display the result
+ */
on_vote: function (event) {
event.stopPropagation();
var self=this;
- return this.ds_message.call('vote_toggle', [[self.datasets.id]]).pipe(function(vote){
- // TDE note: to update, because vote_user_ids is about to disappear to be replaced by vote_nb (number of votes)
- self.datasets.has_voted=vote;
- if (!self.datasets.has_voted) {
- var votes=[];
- for(var i in self.datasets.vote_user_ids){
- if(self.datasets.vote_user_ids[i][0]!=self.datasets.session.uid)
- vote.push(self.datasets.vote_user_ids[i]);
- }
- self.datasets.vote_user_ids=votes;
- }
- else {
- self.datasets.vote_user_ids.push([self.session.uid, 'You']);
- }
+ return this.ds_message.call('vote_toggle', [[self.id]]).pipe(function (vote) {
+ self.has_voted = vote;
+ self.vote_nb += self.has_voted ? 1 : -1;
self.display_vote();
});
return false;
},
- // Render vote Display template.
+ /**
+ * Display the render of this message's vote
+ */
display_vote: function () {
var self = this;
var vote_element = session.web.qweb.render('mail.thread.message.vote', {'widget': self});
- self.$(".placeholder-mail-vote:first").empty();
- self.$(".placeholder-mail-vote:first").html(vote_element);
+ self.$(".oe_msg_vote:first").remove();
+ self.$(".oe_mail_vote_count:first").replaceWith(vote_element);
},
- // Stared/unstared + Render star.
+ /**
+ * add or remove a favorite (or starred) for a message and change class on the DOM
+ */
on_star: function (event) {
event.stopPropagation();
var self=this;
- var button = self.$('button.oe_mail_starbox:first');
- return this.ds_message.call('favorite_toggle', [[self.datasets.id]]).pipe(function(star){
- self.datasets.is_favorite=star;
- if(self.datasets.is_favorite){
- button.addClass('oe_stared');
+ var button = self.$('.oe_star:first');
+ return this.ds_message.call('favorite_toggle', [[self.id]]).pipe(function (star) {
+ self.is_favorite=star;
+ if (self.is_favorite) {
+ button.addClass('oe_starred');
} else {
- button.removeClass('oe_stared');
- if( self.options.thread.typeof_thread == 'stared' ) {
- self.animated_destroy({fadeTime:250});
+ button.removeClass('oe_starred');
+ if ( self.options.typeof_thread == 'stared' ) {
+ self.animated_destroy(150);
}
}
});
return false;
},
+ /**
+ * call on_message_delete on his parent thread
+ */
+ destroy: function () {
+
+ this._super();
+ this.parent_thread.on_message_detroy(this);
+
+ }
+
});
- /**
- * ------------------------------------------------------------
+ /**
+ * ------------------------------------------------------------
* Thread Widget
* ------------------------------------------------------------
*
@@ -799,23 +878,15 @@ openerp.mail = function(session) {
* @param {Object} [options]
* @param {Object} [message] read about mail.ThreadMessage object
* @param {Object} [thread]
- * @param {Boolean} [use_composer] use the advanced composer, or
- * the default basic textarea if not set
- * @param {Number} [expandable_number=5] number message show
- * for each click on "show more message"
- * @param {Number} [expandable_default_number=5] number message show
- * on begin before the first click on "show more message"
- * @param {Array [A,B]} [display_on_thread] display the threads (hierarchy)
- * for the thread level between A and B. -1 for no begin or no end.
- * All thread before A are insert in the root thread.
- * All thread after B are insert in parent thread on B level.
+ * @param {int} [display_indented_thread] number thread level to indented threads.
+ * other are on flat mode
* @param {Select} [typeof_thread] inbox/archives/stared/sent
* type of thread and option for user application like animate
* destroy for read/unread
* @param {Array} [parents] liked with the parents thread
* use with browse, fetch... [O]= top parent
*/
- init: function(parent, options) {
+ init: function (parent, datasets, options) {
this._super(parent);
this.domain = options.domain || [];
this.context = _.extend({
@@ -823,154 +894,132 @@ openerp.mail = function(session) {
default_res_id: 0,
default_parent_id: false }, options.context || {});
- // options
- this.options={
- 'thread' : {
- 'show_header_compose': (options.options.thread.show_header_compose != undefined ? options.options.thread.show_header_compose: false),
- 'use_composer': options.options.thread.use_composer || false,
- 'expandable_number': options.options.thread.expandable_number || 5,
- 'expandable_default_number': options.options.thread.expandable_default_number || 5,
- '_expandable_max': options.options.thread.expandable_default_number || 5,
- 'display_on_thread': options.options.thread.display_on_thread || [0,-1],
- 'typeof_thread': options.options.thread.typeof_thread || 'inbox',
- '_parents': (options.options.thread._parents != undefined ? options.options.thread._parents : []).concat( [this] )
- },
- 'message' : options.options.message
- };
+ this.options = options.options;
+ this.options._parents = (options.options._parents != undefined ? options.options._parents : []).concat( [this] );
// record options and data
this.parent_message= parent.thread!= undefined ? parent : false ;
- var param = options.datasets
- // datasets and internal vars
- this.datasets = {
- 'id' : param.id || false,
- 'model' : param.model || false,
- 'parent_id' : param.parent_id || false,
- 'is_private' : param.is_private || false,
- 'author_id' : param.author_id || false,
- 'thread_level' : (param.thread_level+1) || 0,
- 'partner_ids' : []
- };
-
- for(var i in param.partner_ids){
- if(param.partner_ids[i][0]!=(param.author_id ? param.author_id[0] : -1)){
- this.datasets.partner_ids.push(param.partner_ids[i]);
- }
- }
-
+ // data of this thread
+ this.id = datasets.id || false,
+ this.model = datasets.model || false,
+ this.parent_id = datasets.parent_id || false,
+ this.is_private = datasets.is_private || false,
+ this.author_id = datasets.author_id || false,
+ this.thread_level = (datasets.thread_level+1) || 0,
+ this.partner_ids = _.filter(datasets.partner_ids, function (partner) { return partner[0]!=datasets.author_id[0]; } )
this.messages = [];
- this.ComposeMessage = false;
+ this.show_compose_message = this.options.show_compose_message && (this.options.show_reply_button > this.thread_level || !this.thread_level);
+
+ // object compose message
+ this.compose_message = false;
this.ds_thread = new session.web.DataSetSearch(this, this.context.default_model || 'mail.thread');
this.ds_message = new session.web.DataSetSearch(this, 'mail.message');
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
-
- this.list_ul = this.$('ul.oe_mail_thread_display:first');
- this.more_msg = this.$(">.oe_msg_more_message:first");
-
- this.display_user_avatar();
- var display_done = compose_done = false;
-
this.bind_events();
-
- if(this.options.thread._parents[0]==this){
- this.on_root_thread();
- }
-
- return display_done && compose_done;
},
- instantiate_ComposeMessage: function() {
- // add message composition form view
- this.ComposeMessage = new mail.ThreadComposeMessage(this,{
- 'context': this.context,
- 'datasets': this.datasets,
- 'options': this.options,
- 'show_attachment_delete': true,
- });
- this.ComposeMessage.appendTo(this.$(".oe_mail_thread_action:first"));
- },
-
- /* this method is runing for first parent thread
+ /* instantiate the compose message object and insert this on the DOM.
+ * The compose message is display in compact form.
*/
- on_root_thread: function(){
- var self=this;
- // fetch and display message, using message_ids if set
- this.message_fetch();
-
- $(document).scroll( self.on_scroll );
- $(window).resize( self.on_scroll );
- window.setTimeout( self.on_scroll, 500 );
-
- $(session.web.qweb.render('mail.wall_no_message', {})).appendTo(this.$('ul.oe_mail_thread_display'));
-
- this.instantiate_ComposeMessage();
- this.ComposeMessage.datasets.is_private=true;
-
- if(this.options.thread.show_header_compose){
- this.ComposeMessage.$el.show();
- //this.ComposeMessage.set_free_attachments();
+ instantiate_compose_message: function () {
+ // add message composition form view
+ if (!this.compose_message) {
+ this.compose_message = new mail.ThreadComposeMessage(this, this, {
+ 'context': this.context,
+ 'options': this.options,
+ });
+ if (!this.thread_level) {
+ // root view
+ this.compose_message.insertBefore(this.$el);
+ } else if (this.thread_level > this.options.display_indented_thread) {
+ this.compose_message.insertAfter(this.$el);
+ } else {
+ this.compose_message.appendTo(this.$el);
+ }
}
-
- this.$el.addClass("oe_mail_root_thread");
},
/* When the expandable object is visible on screen (with scrolling)
* then the on_expandable function is launch
*/
- on_scroll: function(event){
- if(event)event.stopPropagation();
- var message = this.messages[0];
- if(message && message.datasets.type=="expandable" && message.datasets.max_limit){
+ on_scroll: function (event) {
+ if (event)event.stopPropagation();
+ this.$('.oe_msg_expandable:last');
+
+ var message = this.messages[this.messages.length-1];
+ if (message && message.type=="expandable" && message.max_limit) {
var pos = message.$el.position();
- if(pos.top){
+ if (pos.top) {
/* bottom of the screen */
var bottom = $(window).scrollTop()+$(window).height()+200;
- if(bottom - pos.top > 0){
+ if (bottom > pos.top) {
message.on_expandable();
}
}
-
}
},
/**
* Bind events in the widget. Each event is slightly described
* in the function. */
- bind_events: function() {
+ bind_events: function () {
var self = this;
- self.$('.oe_mail_compose_textarea .oe_more').click(function () { var p=$(this).parent(); p.find('.oe_more_hidden, .oe_hidden').show(); p.find('.oe_more').hide(); });
- self.$('.oe_mail_compose_textarea .oe_more_hidden').click(function () { var p=$(this).parent(); p.find('.oe_more_hidden, .oe_hidden').hide(); p.find('.oe_more').show(); });
+ self.$el.on('click', '.oe_mail_list_recipients .oe_more', self.on_show_recipients);
+ self.$el.on('click', '.oe_mail_compose_textarea .oe_more_hidden', self.on_hide_recipients);
},
- /* get all child message/thread id linked
+ /**
+ *show all the partner list of this parent message
*/
- get_child_ids: function(){
+ on_show_recipients: function () {
+ var p=$(this).parent();
+ p.find('.oe_more_hidden, .oe_hidden').show();
+ p.find('.oe_more').hide();
+ },
+
+ /**
+ *hide a part of the partner list of this parent message
+ */
+ on_hide_recipients: function () {
+ var p=$(this).parent();
+ p.find('.oe_more_hidden, .oe_hidden').hide();
+ p.find('.oe_more').show();
+ },
+
+ /* get all child message/thread id linked.
+ * @return array of id
+ */
+ get_child_ids: function () {
var res=[];
- _(this.get_childs()).each(function (val, key) { res.push(val.datasets.id); });
+ _(this.get_childs()).each(function (val, key) { res.push(val.id); });
return res;
},
- /* get all child message/thread linked
+ /* get all child message/thread linked.
+ * @param {int} nb_thread_level, number of traversed thread level for this search
+ * @return array of thread object
*/
- get_childs: function(nb_thread_level){
+ get_childs: function (nb_thread_level) {
var res=[];
- if(arguments[1]) res.push(this);
- if(isNaN(nb_thread_level) || nb_thread_level>0){
+ if (arguments[1]) res.push(this);
+ if (isNaN(nb_thread_level) || nb_thread_level>0) {
_(this.messages).each(function (val, key) {
- if(val.thread){
- res = res.concat( val.thread.get_childs((isNaN(nb_thread_level) ? null : nb_thread_level-1), true) )
+ if (val.thread) {
+ res = res.concat( val.thread.get_childs((isNaN(nb_thread_level) ? undefined : nb_thread_level-1), true) );
}
});
}
return res;
},
- /** browse thread
+ /**
+ *search a thread in all thread and child thread.
+ * This method return an object thread.
* @param {object}{int} option.id
* @param {object}{string} option.model
* @param {object}{boolean} option._go_thread_wall
@@ -979,252 +1028,469 @@ openerp.mail = function(session) {
* return the top thread (wall) if no thread found
* @return thread object
*/
- browse_thread: function(options){
+ browse_thread: function (options) {
// goto the wall thread for launch browse
- if(!options._go_thread_wall) {
+ if (!options._go_thread_wall) {
options._go_thread_wall = true;
- return this.options.thread._parents[0].browse_thread(options);
+ return this.options._parents[0].browse_thread(options);
}
- if(this.datasets.id==options.id){
+ if (this.id==options.id) {
return this;
}
- if(options.id)
- for(var i in this.messages){
- if(this.messages[i].thread){
- var res=this.messages[i].thread.browse_thread({'id':options.id, '_go_thread_wall':true});
- if(res) return res;
+ if (options.id) {
+ for (var i in this.messages) {
+ if (this.messages[i].thread) {
+ var res=this.messages[i].thread.browse_thread({'id':options.id, '_go_thread_wall':true});
+ if (res) return res;
+ }
}
}
//if option default_return_top_thread, return the top if no found thread
- if(options.default_return_top_thread){
+ if (options.default_return_top_thread) {
return this;
}
return false;
},
- /** browse message
+ /**
+ *search a message in all thread and child thread.
+ * This method return an object message.
* @param {object}{int} option.id
* @param {object}{string} option.model
* @param {object}{boolean} option._go_thread_wall
* private for check the top thread
- * @return thread object
+ * @return message object
*/
- browse_message: function(options){
- if(this.options.thread._parents[0].messages[0])
- return this.options.thread._parents[0].messages[0].browse_message(options);
+ browse_message: function (options) {
+ if (this.options._parents[0].messages[0])
+ return this.options._parents[0].messages[0].browse_message(options);
+ },
+
+ /**
+ *If compose_message doesn't exist, instantiate the compose message.
+ * Call the on_compose_expandable method to allow the user to write his message.
+ * (Is call when a user click on "Reply" button)
+ */
+ on_compose_message: function () {
+ this.instantiate_compose_message();
+ this.compose_message.on_compose_expandable();
},
- /* this function is launch when a user click on "Reply" button
+ /**
+ *display the message "there are no message" on the thread
*/
- on_compose_message: function(){
- if(!this.ComposeMessage){
- this.instantiate_ComposeMessage();
+ no_message: function () {
+ var no_message = $(session.web.qweb.render('mail.wall_no_message', {}));
+ if (this.options.no_message) {
+ no_message.html(this.options.no_message);
}
- this.ComposeMessage.$el.toggle();
- return false;
+ no_message.appendTo(this.$el);
},
- /** Fetch messages
- * @param {Bool} initial_mode: initial mode: try to use message_data or
- * message_ids, if nothing available perform a message_read; otherwise
- * directly perform a message_read
+ /**
+ *make a request to read the message (calling RPC to "message_read").
+ * The result of this method is send to the switch message for sending ach message to
+ * his parented object thread.
* @param {Array} replace_domain: added to this.domain
* @param {Object} replace_context: added to this.context
+ * @param {Array} ids read (if the are some ids, the method don't use the domain)
*/
- message_fetch: function (initial_mode, replace_domain, replace_context, ids, callback) {
+ message_fetch: function (replace_domain, replace_context, ids, callback) {
var self = this;
- // initial mode: try to use message_data or message_ids
- if (initial_mode && this.options.thread.message_data) {
- return this.create_message_object(this.options.message_data);
- }
// domain and context: options + additional
fetch_domain = replace_domain ? replace_domain : this.domain;
fetch_context = replace_context ? replace_context : this.context;
- var message_loaded = [this.datasets.id||0].concat( self.options.thread._parents[0].get_child_ids() );
+ var message_loaded_ids = this.id ? [this.id].concat( self.get_child_ids() ) : self.get_child_ids();
- return this.ds_message.call('message_read', [ids, fetch_domain, message_loaded, fetch_context, this.context.default_parent_id || undefined]
- ).then(this.proxy('switch_new_message'));
+ // CHM note : option for sending in flat mode by server
+ var thread_level = this.options.display_indented_thread > this.thread_level ? this.options.display_indented_thread - this.thread_level : 0;
+
+ return this.ds_message.call('message_read', [ids, fetch_domain, message_loaded_ids, thread_level, fetch_context, this.context.default_parent_id || undefined])
+ .then(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message'));
},
- /* create record object and linked him
+ /**
+ *create the message object and attached on this thread.
+ * When the message object is create, this method call insert_message for,
+ * displaying this message on the DOM.
+ * @param : {object} data from calling RPC to "message_read"
*/
create_message_object: function (data) {
var self = this;
- if(data.type=='expandable'){
- var message = new mail.ThreadExpandable(self, {
- 'domain': data.domain,
- 'context': {
- 'default_model': data.model || self.context.default_model,
- 'default_res_id': data.res_id || self.context.default_res_id,
- 'default_parent_id': self.datasets.id },
- 'datasets': data
+ var data = _.extend(data, {'thread_level': data.thread_level ? data.thread_level : self.thread_level});
+ data.options = _.extend(self.options, data.options);
+
+ if (data.type=='expandable') {
+ var message = new mail.ThreadExpandable(self, data, {
+ 'default_model': data.model || self.context.default_model,
+ 'default_res_id': data.res_id || self.context.default_res_id,
+ 'default_parent_id': self.id,
});
} else {
- var message = new mail.ThreadMessage(self, {
- 'domain': data.domain,
- 'context': {
- 'default_model': data.model,
- 'default_res_id': data.res_id,
- 'default_parent_id': data.id },
- 'options':{
- 'thread': self.options.thread,
- 'message': self.options.message
- },
- 'datasets': _.extend(data, {'thread_level': self.datasets.thread_level})
+ var message = new mail.ThreadMessage(self, data, {
+ 'default_model': data.model,
+ 'default_res_id': data.res_id,
+ 'default_parent_id': data.id,
});
- var data = _.extend(data, {'thread_level': self.datasets.thread_level});
}
// check if the message is already create
- for(var i in self.messages){
- if(self.messages[i].datasets.id==message.datasets.id){
+ for (var i in self.messages) {
+ if (self.messages[i] && self.messages[i].id == message.id) {
self.messages[i].destroy();
- self.messages[i]=self.insert_message(message);
- return true;
}
}
- self.messages.push( self.insert_message(message) );
+ self.messages.push( message );
+
+ return message;
},
- /** Displays a message or an expandable message */
- insert_message: function (message) {
+ /**
+ *insert the message on the DOM.
+ * All message (and expandable message) are sorted. The method get the
+ * older and newer message to insert the message (before, after).
+ * If there are no older or newer, the message is prepend or append to
+ * the thread (parent object or on root thread for flat view).
+ * The sort is define by the thread_level (O for newer on top).
+ * @param : {object} ThreadMessage object
+ */
+ insert_message: function (message, dom_insert_after) {
var self=this;
- this.$("li.oe_wall_no_message").remove();
+ if (this.show_compose_message && this.options.show_compact_message) {
+ this.instantiate_compose_message();
+ this.compose_message.do_show_compact();
+ }
- // insert on hierarchy display => insert in self child
- var thread_messages = self.messages;
- var thread = self;
- var flat = false;
- var hierarchy = self.options.thread.display_on_thread;
- if( hierarchy[0] < 0 ||
- hierarchy[0] > self.datasets.thread_level ||
- (hierarchy[1]>0 && hierarchy[1] < self.datasets.thread_level) ) {
+ this.$('.oe_wall_no_message').remove();
- var flat = true;
- if(hierarchy[0]<0){
-
- // all is in flat mode
- thread = self.options.thread._parents[0];
- var nb_thread_level = null;
-
- } else if(hierarchy[0] > self.datasets.thread_level) {
-
- // list all childs messages for flat display before the hierarchy
- thread = self.options.thread._parents[0];
- var nb_thread_level = hierarchy[0];
-
- } else if(hierarchy[1] < self.datasets.thread_level) {
-
- // list all childs messages for flat display after the hierarchy
- thread = self.options.thread._parents[hierarchy[1]];
- var nb_thread_level = hierarchy[1]>0 ? hierarchy[1]-hierarchy[0] : null;
- } else {
+ if (dom_insert_after) {
+ message.insertAfter(dom_insert_after);
+ return message
+ }
- thread = self.options.thread._parents[0];
- var nb_thread_level = null;
+ // check older and newer message for insertion
+ var message_newer = false;
+ var message_older = false;
+ if (message.id > 0) {
+ for (var i in self.messages) {
+ if (self.messages[i].id > message.id) {
+ if (!message_newer || message_newer.id > self.messages[i].id) {
+ message_newer = self.messages[i];
+ }
+ } else if (self.messages[i].id > 0 && self.messages[i].id < message.id) {
+ if (!message_older || message_older.id < self.messages[i].id) {
+ message_older = self.messages[i];
+ }
+ }
}
-
- var thread_messages = [];
- _(thread.get_childs( nb_thread_level )).each(function (val, key) { thread_messages.push(val.parent_message); });
}
+ var sort = (!!self.thread_level || message.id<0);
- // check older and newer message for insert
- var parent_newer = false;
- var parent_older = false;
- if ( message.datasets.id > 0 ){
- for(var i in thread_messages){
- if(thread_messages[i].datasets.id > message.datasets.id){
- if(!parent_newer || parent_newer.datasets.id>=thread_messages[i].datasets.id)
- parent_newer = thread_messages[i];
- } else if(thread_messages[i].datasets.id>0 && thread_messages[i].datasets.id < message.datasets.id) {
- if(!parent_older || parent_older.id<thread_messages[i].datasets.id)
- parent_older = thread_messages[i];
- }
- }
- }
+ if (sort) {
+ if (message_older) {
- var sort = self.datasets.thread_level==0 || (flat && self.datasets.thread_level>=1);
+ message.insertAfter(message_older.thread ? (message_older.thread.compose_message ? message_older.thread.compose_message.$el : message_older.thread.$el) : message_older.$el);
+
+ } else if (message_newer) {
+
+ message.insertBefore(message_newer.$el);
+
+ } else if (message.id < 0) {
+
+ message.appendTo(self.$el);
- if(parent_older){
- if(sort){
- message.insertBefore(parent_older.$el);
- } else {
- message.insertAfter(parent_older.$el);
- }
- } else if(parent_newer){
- if(sort){
- message.insertAfter(parent_newer.$el);
} else {
- message.insertBefore(parent_newer.$el);
+
+ message.prependTo(self.$el);
}
} else {
- if(sort && message.id > 0){
- message.prependTo(thread.list_ul);
+ if (message_older) {
+
+ message.insertBefore(message_older.$el);
+
+ } else if (message_newer) {
+
+ message.insertAfter(message_newer.thread ? (message_newer.thread.compose_message ? message_newer.thread.compose_message.$el : message_newer.thread.$el) : message_newer.$el );
+
+ } else if (message.id < 0) {
+
+ message.prependTo(self.$el);
+
} else {
- message.appendTo(thread.list_ul);
+
+ message.appendTo(self.$el);
+
}
}
return message
},
-
- display_user_avatar: function () {
- var avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
- return this.$('img.oe_mail_icon').attr('src', avatar);
- },
- /* Send the records to his parent thread */
- switch_new_message: function(records) {
+ /**
+ *get the parent thread of the messages.
+ * Each message is send to his parent object (or parent thread flat mode) for creating the object message.
+ * @param : {Array} datas from calling RPC to "message_read"
+ */
+ switch_new_message: function (records) {
var self=this;
- _(records).each(function(record){
- self.browse_thread({
+ _(records).each(function (record) {
+ var thread = self.browse_thread({
'id': record.parent_id,
'default_return_top_thread':true
- }).create_message_object( record );
+ });
+ // create object and attach to the thread object
+ var message = thread.create_message_object( record );
+ // insert the message on dom
+ thread.insert_message( message );
});
},
- });
+ /**
+ * this method is call when the widget of a message or an expandable message is destroy
+ * in this thread. The this.messages array is filter to remove this message
+ */
+ on_message_detroy: function (message) {
+
+ this.messages = _.filter(this.messages, function (val) { return !val.isDestroyed(); });
+
+ },
+
+ /**
+ * Convert a destroyed message into a expandable message
+ */
+ message_to_expandable: function (message) {
+
+ if (!this.thread_level || message.isDestroyed()) {
+ message.destroy();
+ return false;
+ }
+
+ var messages = _.sortBy( this.messages, function (val) { return val.id; });
+ var it = _.indexOf( messages, message );
+
+ var msg_up = messages[it-1];
+ var msg_down = messages[it+1];
+
+ var message_dom = [ ["id", "=", message.id] ];
+
+ if ( msg_up && msg_up.type == "expandable" && msg_down && msg_down.type == "expandable") {
+ // concat two expandable message and add this message to this dom
+ msg_up.domain = mail.ChatterUtils.expand_domain( msg_up.domain );
+ msg_down.domain = mail.ChatterUtils.expand_domain( msg_down.domain );
- /**
+ msg_down.domain = ['|','|'].concat( msg_up.domain ).concat( message_dom ).concat( msg_down.domain );
+
+ if ( !msg_down.max_limit ) {
+ msg_down.nb_messages += 1 + msg_up.nb_messages;
+ }
+
+ msg_up.$el.remove();
+ msg_up.destroy();
+
+ msg_down.reinit();
+
+ } else if ( msg_up && msg_up.type == "expandable") {
+ // concat preview expandable message and this message to this dom
+ msg_up.domain = mail.ChatterUtils.expand_domain( msg_up.domain );
+ msg_up.domain = ['|'].concat( msg_up.domain ).concat( message_dom );
+
+ msg_up.nb_messages++;
+
+ msg_up.reinit();
+
+ } else if ( msg_down && msg_down.type == "expandable") {
+ // concat next expandable message and this message to this dom
+ msg_down.domain = mail.ChatterUtils.expand_domain( msg_down.domain );
+ msg_down.domain = ['|'].concat( msg_down.domain ).concat( message_dom );
+
+ // it's maybe a message expandable for the max limit read message
+ if ( !msg_down.max_limit ) {
+ msg_down.nb_messages++;
+ }
+
+ msg_down.reinit();
+
+ } else {
+ // create a expandable message
+ var expandable = new mail.ThreadExpandable(this, {
+ 'id': message.id,
+ 'model': message.model,
+ 'parent_id': message.parent_id,
+ 'nb_messages': 1,
+ 'thread_level': message.thread_level,
+ 'parent_id': message.parent_id,
+ 'domain': message_dom,
+ 'options': message.options,
+ }, {
+ 'default_model': message.model || this.context.default_model,
+ 'default_res_id': message.res_id || this.context.default_res_id,
+ 'default_parent_id': this.id,
+ });
+
+ // add object on array and DOM
+ this.messages.push(expandable);
+ expandable.insertAfter(message.$el);
+ }
+
+ // destroy message
+ message.destroy();
+
+ return true;
+ },
+ });
+
+ /**
+ * ------------------------------------------------------------
+ * mail : root Widget
* ------------------------------------------------------------
+ *
+ * This widget handles the display of messages with thread options. Its main
+ * use is to receive a context and a domain, and to delegate the message
+ * fetching and displaying to the Thread widget.
+ */
+ session.web.client_actions.add('mail.Widget', 'session.mail.Widget');
+ mail.Widget = session.web.Widget.extend({
+ template: 'mail.Widget',
+
+ /**
+ * @param {Object} parent parent
+ * @param {Array} [domain]
+ * @param {Object} [context] context of the thread. It should
+ * contain at least default_model, default_res_id. Please refer to
+ * the compose_message widget for more information about it.
+ * ... @param {Select} [typeof_thread=(mail|stared|archives|send|other)]
+ * options for destroy message when the user click on a button
+ * @param {Object} [options]
+ *... @param {Number} [truncate_limit=250] number of character to
+ * display before having a "show more" link; note that the text
+ * will not be truncated if it does not have 110% of the parameter
+ *... @param {Boolean} [show_record_name] display the name and link for do action
+ *... @param {int} [show_reply_button] number thread level to display the reply button
+ *... @param {int} [show_read_unread_button] number thread level to display the read/unread button
+ *... @param {int} [display_indented_thread] number thread level to indented threads.
+ * other are on flat mode
+ *... @param {Boolean} [show_compose_message] allow to display the composer
+ *... @param {Boolean} [show_compact_message] display the compact message on the thread
+ * when the user clic on this compact mode, the composer is open
+ *... @param {Array} [message_ids] List of ids to fetch by the root thread.
+ * When you use this option, the domain is not used for the fetch root.
+ * @param {String} [no_message] Message to display when there are no message
+ */
+ init: function (parent, options) {
+ this._super(parent);
+ this.domain = options.domain || [];
+ this.context = options.context || {};
+ this.search_results = {'domain': [], 'context': {}, 'groupby': {}};
+
+ this.options = _.extend({
+ 'typeof_thread' : 'inbox',
+ 'display_indented_thread' : -1,
+ 'show_reply_button' : -1,
+ 'show_read_unread_button' : -1,
+ 'truncate_limit' : 250,
+ 'show_record_name' : false,
+ 'show_compose_message' : false,
+ 'show_compact_message' : false,
+ 'message_ids': undefined,
+ 'no_message': false
+ }, options);
+
+ if (this.display_indented_thread === false) {
+ this.display_indented_thread = -1;
+ }
+ if (this.show_reply_button === false) {
+ this.show_reply_button = -1;
+ }
+ if (this.show_read_unread_button === false) {
+ this.show_read_unread_button = -1;
+ }
+
+ },
+
+ start: function (options) {
+ this._super.apply(this, arguments);
+ this.message_render();
+ this.bind_events();
+ },
+
+
+ /**
+ *Create the root thread and display this object in the DOM.
+ * Call the no_message method then c all the message_fetch method
+ * of this root thread to display the messages.
+ */
+ message_render: function (search) {
+
+ this.thread = new mail.Thread(this, {}, {
+ 'domain' : this.domain,
+ 'context' : this.context,
+ 'options': this.options,
+ });
+
+ this.thread.appendTo( this.$el );
+ this.thread.no_message();
+ this.thread.message_fetch(null, null, this.options.message_ids);
+
+ if (this.options.show_compose_message) {
+ this.thread.instantiate_compose_message();
+ if (this.options.show_compact_message) {
+ this.thread.compose_message.do_show_compact();
+ } else {
+ this.thread.compose_message.do_hide_compact();
+ }
+ }
+ },
+
+ bind_events: function () {
+ if (this.context['typeof_thread']!='other') {
+ $(document).scroll( this.thread.on_scroll );
+ $(window).resize( this.thread.on_scroll );
+ window.setTimeout( this.thread.on_scroll, 500 );
+ }
+ }
+ });
+
+
+ /**
+ * ------------------------------------------------------------
* mail_thread Widget
* ------------------------------------------------------------
*
* This widget handles the display of messages on a document. Its main
* use is to receive a context and a domain, and to delegate the message
* fetching and displaying to the Thread widget.
+ * Use Help on the field to display a custom "no message loaded"
*/
session.web.form.widgets.add('mail_thread', 'openerp.mail.RecordThread');
mail.RecordThread = session.web.form.AbstractField.extend({
template: 'mail.record_thread',
- init: function() {
+ init: function () {
this._super.apply(this, arguments);
this.options.domain = this.options.domain || [];
this.options.context = {'default_model': 'mail.thread', 'default_res_id': false};
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
// NB: check the actual_mode property on view to know if the view is in create mode anymore
this.view.on("change:actual_mode", this, this._check_visibility);
this._check_visibility();
},
- _check_visibility: function() {
+ _check_visibility: function () {
this.$el.toggle(this.view.get("actual_mode") !== "create");
},
- render_value: function() {
+ render_value: function () {
var self = this;
if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
this.$('oe_mail_thread').hide();
@@ -1237,39 +1503,37 @@ openerp.mail = function(session) {
default_is_private: false });
// update domain
var domain = this.options.domain.concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]);
- // create and render Thread widget
- // TDE note: replace message_is_follower by a check in message_follower_ids, as message_is_follower is not used in views anymore
- var show_header_compose = this.view.is_action_enabled('edit') ||
+
+ var show_compose_message = this.view.is_action_enabled('edit') ||
(this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value());
- if(this.thread){
- this.thread.destroy();
+ var message_ids = this.getParent().fields.message_ids && this.getParent().fields.message_ids.get_value();
+
+ if (this.root) {
+ this.root.destroy();
}
- this.thread = new mail.Thread(self, {
- 'domain': domain,
- 'context': this.options.context,
- 'options':{
- 'thread':{
- 'show_header_compose': show_header_compose,
- 'use_composer': show_header_compose,
- 'display_on_thread':[-1,-1]
- },
- 'message':{
- 'show_reply': [-1,-1],
- 'show_read_unread': [-1,-1],
- 'show_dd_delete': false
- }
- },
- 'datasets': {},
+ // create and render Thread widget
+ this.root = new mail.Widget(this, {
+ 'domain' : domain,
+ 'context' : this.options.context,
+ 'typeof_thread': this.options.context['typeof_thread'] || 'other',
+ 'display_indented_thread': -1,
+ 'show_reply_button': 0,
+ 'show_read_unread_button': -1,
+ 'show_compose_message': show_compose_message,
+ 'message_ids': message_ids,
+ 'show_compact_message': true,
+ 'no_message': this.node.attrs.help
}
);
- return this.thread.appendTo( this.$('.oe_mail_wall_threads:first') );
+
+ return this.root.replace(this.$('.oe_mail-placeholder'));
},
});
- /**
- * ------------------------------------------------------------
+ /**
+ * ------------------------------------------------------------
* Wall Widget
* ------------------------------------------------------------
*
@@ -1287,8 +1551,6 @@ openerp.mail = function(session) {
* @param {Array} [options.domain] domain on the Wall
* @param {Object} [options.context] context, is an object. It should
* contain default_model, default_res_id, to give it to the threads.
- * @param {Number} [options.thread_level] number of thread levels to display
- * 0 being flat.
*/
init: function (parent, options) {
this._super(parent);
@@ -1305,7 +1567,7 @@ openerp.mail = function(session) {
var thread_displayed = this.message_render();
this.options.domain = this.options.domain.concat(this.search_results['domain']);
this.bind_events();
- return (searchview_ready && thread_displayed);
+ return $.when(searchview_ready, thread_displayed);
},
/**
@@ -1328,7 +1590,7 @@ openerp.mail = function(session) {
* @param {Array} contexts
* @param {Array} groupbys
*/
- do_searchview_search: function(domains, contexts, groupbys) {
+ do_searchview_search: function (domains, contexts, groupbys) {
var self = this;
this.rpc('/web/session/eval_domain_and_context', {
domains: domains || [],
@@ -1337,51 +1599,43 @@ openerp.mail = function(session) {
}).then(function (results) {
self.search_results['context'] = results.context;
self.search_results['domain'] = results.domain;
- self.thread.destroy();
+ self.root.destroy();
return self.message_render();
});
},
/**
- * Display the threads
+ *Create the root thread widget and display this object in the DOM
*/
message_render: function (search) {
var domain = this.options.domain.concat(this.search_results['domain']);
var context = _.extend(this.options.context, search&&search.search_results['context'] ? search.search_results['context'] : {});
- this.thread = new mail.Thread(this, {
- 'domain' : domain,
- 'context' : context,
- 'options': {
- 'thread' :{
- 'use_composer': true,
- 'show_header_compose': false,
- 'typeof_thread': context.typeof_thread || 'inbox',
- 'display_on_thread': [0,1]
- },
- 'message': {
- 'show_reply': [0,0],
- 'show_read_unread': [0,-1],
- 'show_dd_delete': false,
- },
- },
- 'datasets': {},
+ this.root = new mail.Widget(this, {
+ 'domain' : domain,
+ 'context' : context,
+ 'typeof_thread': context['typeof_thread'] || 'other',
+ 'display_indented_thread': 1,
+ 'show_reply_button': 10,
+ 'show_read_unread_button': 11,
+ 'show_compose_message': true,
+ 'show_compact_message': false,
}
);
- return this.thread.appendTo( this.$('.oe_mail_wall_threads:first') );
+ return this.root.replace(this.$('.oe_mail-placeholder'));
},
- bind_events: function(){
+ bind_events: function () {
var self=this;
- this.$("button.oe_write_full:first").click(function(){ self.thread.ComposeMessage.on_compose_fullmail(); });
- this.$("button.oe_write_onwall:first").click(function(){ self.thread.ComposeMessage.$el.toggle(); });
+ this.$(".oe_write_full").click(function(){ self.root.thread.compose_message.on_compose_fullmail(); });
+ this.$(".oe_write_onwall").click(function(){ self.root.thread.on_compose_message(); });
}
});
/**
- * ------------------------------------------------------------
+ * ------------------------------------------------------------
* UserMenu
* ------------------------------------------------------------
*
@@ -1401,13 +1655,13 @@ openerp.mail = function(session) {
};
},
- start: function(parent, params) {
+ start: function (parent, params) {
var self = this;
this.$el.on('click', 'button', self.on_compose_message );
this._super(parent, params);
},
- on_compose_message: function(event){
+ on_compose_message: function (event) {
event.stopPropagation();
var action = {
type: 'ir.actions.act_window',
@@ -1417,7 +1671,11 @@ openerp.mail = function(session) {
action_from: 'mail.ThreadComposeMessage',
views: [[false, 'form']],
target: 'new',
- context: this.options.context,
+ context: _.extend(this.options.context, {
+ 'default_model': this.context.default_model,
+ 'default_res_id': this.context.default_res_id,
+ 'default_content_subtype': 'html',
+ }),
};
session.client.action_manager.do_action(action);
},
@@ -1425,7 +1683,7 @@ openerp.mail = function(session) {
});
session.web.UserMenu = session.web.UserMenu.extend({
- start: function(parent, params) {
+ start: function (parent, params) {
var render = new session.web.ComposeMessageTopButton();
render.insertAfter(this.$el);
this._super(parent, params);
diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js
index 806bf28e7b2..a13d2048bf3 100644
--- a/addons/mail/static/src/js/mail_followers.js
+++ b/addons/mail/static/src/js/mail_followers.js
@@ -24,10 +24,9 @@ openerp_mail_followers = function(session, mail) {
init: function() {
this._super.apply(this, arguments);
- this.options.image = this.node.attrs.image || 'image_small';
- this.options.title = this.node.attrs.title || 'Followers';
- this.options.comment = this.node.attrs.help || false;
- this.options.displayed_nb = this.node.attrs.displayed_nb || 10;
+ this.image = this.node.attrs.image || 'image_small';
+ this.comment = this.node.attrs.help || false;
+ this.displayed_nb = this.node.attrs.displayed_nb || 10;
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
this.ds_users = new session.web.DataSetSearch(this, 'res.users');
@@ -61,7 +60,7 @@ openerp_mail_followers = function(session, mail) {
self.do_unfollow();
});
// event: click on a subtype, that (un)subscribe for this subtype
- this.$el.on('click', 'ul.oe_subtypes input', self.do_update_subscription);
+ this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
// event: click on 'invite' button, that opens the invite wizard
this.$('.oe_invite').on('click', function (event) {
action = {
@@ -115,20 +114,24 @@ openerp_mail_followers = function(session, mail) {
self.message_is_follower = (_.indexOf(self.get('value'), pid) != -1);
}).pipe(self.proxy('display_generic'));
},
-
+ _format_followers: function(count){
+ // TDE note: why redefining _t ?
+ function _t(str) { return str; }
+ var str = '';
+ if(count <= 0){
+ str = _t('No followers');
+ }else if(count === 1){
+ str = _t('One follower');
+ }else{
+ str = ''+count+' '+_t('followers');
+ }
+ return str;
+ },
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
var self = this;
- var node_user_list = this.$('ul.oe_mail_followers_display').empty();
- // format content: Followers (You and 0 other) // Followers (3)
- var content = this.options.title;
- if (this.message_is_follower) {
- content += ' (You and ' + (this.get('value').length-1) + ' other)';
- }
- else {
- content += ' (' + this.get('value').length + ')'
- }
- this.$('div.oe_mail_recthread_followers h4').html(content);
+ var node_user_list = this.$('.oe_follower_list').empty();
+ this.$('.oe_follower_title').html(this._format_followers(this.get('value').length));
},
/** Display the followers */
@@ -137,16 +140,17 @@ openerp_mail_followers = function(session, mail) {
records = records || [];
this.message_is_follower = this.set_is_follower(records);
// clean and display title
- var node_user_list = this.$('ul.oe_mail_followers_display').empty();
- this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
+ var node_user_list = this.$('.oe_follower_list').empty();
+ this.$('.oe_follower_title').html(this._format_followers(records.length));
// truncate number of displayed followers
- truncated = records.splice(0, this.options.displayed_nb);
+ truncated = records.splice(0, this.displayed_nb);
_(truncated).each(function (record) {
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
});
+ // FVA note: be sure it is correctly translated
if (truncated.length < records.length) {
- $('<li>And ' + (records.length - truncated.length) + ' more.</li>').appendTo(node_user_list);
+ $('<div class="oe_partner">And ' + (records.length - truncated.length) + ' more.</div>').appendTo(node_user_list);
}
},
@@ -172,7 +176,7 @@ openerp_mail_followers = function(session, mail) {
/** Fetch subtypes, only if current user is follower */
fetch_subtypes: function () {
- var subtype_list_ul = this.$('.oe_subtypes').empty();
+ var subtype_list_ul = this.$('.oe_subtype_list').empty();
if (! this.message_is_follower) return;
var context = new session.web.CompoundContext(this.build_context(), {});
this.ds_model.call('message_get_subscription_data', [[this.view.datarecord.id], context]).pipe(this.proxy('display_subtypes'));
@@ -181,13 +185,12 @@ openerp_mail_followers = function(session, mail) {
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data) {
var self = this;
- var subtype_list_ul = this.$('.oe_subtypes');
- var records = data[this.view.datarecord.id].message_subtype_data;
-
+ var subtype_list_ul = this.$('.oe_subtype_list');
+ var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
- $(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('ul.oe_subtypes') );
+ $(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
});
},
@@ -210,7 +213,7 @@ openerp_mail_followers = function(session, mail) {
var self = this;
var checklist = new Array();
- _(this.$('.oe_mail_recthread_actions input[type="checkbox"]')).each(function (record) {
+ _(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
}
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index fbb64cc6772..caa85cd3deb 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -1,33 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
+ <!-- this template contains the mail widget and is used to namespace the css -->
+ <t t-name="mail.Widget">
+ <div class="oe_mail">
+ </div>
+ </t>
+
<!--
mail.compose_message template
This template holds the composition form to write a note or send
an e-mail. It contains by default a textarea, that will be replaced
by another composition form in the main wall composition form, or
for main thread composition form in document form view.
+
+ mail.compose_message.compact template
+ This template holds the composition form to write a message, this box is converted into
+ mail.compose_message when focus on textarea
-->
<t t-name="mail.compose_message">
- <div class="oe_mail_compose_textarea">
- <img class="oe_mail_icon oe_mail_frame oe_left" alt="User img"/>
- <div class="oe_msg_content">
- <!-- contains the composition form -->
- <!-- default content: old basic textarea -->
- <div class="oe_mail_post_header">
+ <div t-if="widget.show_composer" t-attf-class="oe_msg oe_msg_composer #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
+ <div class="oe_msg_left">
+ <img class="oe_msg_icon" alt="User img" t-attf-src="#{widget.avatar}"/>
+ </div>
+ <div class="oe_msg_center">
+ <div class="oe_msg_content">
<t t-call="mail.thread.list_recipients"/>
- <a class="oe_cancel oe_e">X</a>
+ <textarea class="field_text"></textarea>
</div>
- <textarea class="field_text" placeholder="Add your comment here..."/>
- <div class="oe_mail_post_footer">
- <div class="oe_mail_compose_attachment_list"/>
- <button class="oe_full">Full mail message</button>
- <button class="oe_post">Post message</button>
+ <div class="oe_msg_footer">
+ <div class="oe_msg_attachment_list"></div>
+ <button class="oe_post">Post</button>
<t t-call="mail.compose_message.add_attachment"/>
+ <!--<a class="oe_cancel oe_e">X</a>-->
+ <button class="oe_full"><span class='oe_e'>&amp;ograve</span></button>
</div>
</div>
- <div class="oe_clear"/>
</div>
+ <div t-if="widget.show_compact_message and !widget.show_composer" t-attf-class="oe_msg oe_msg_composer_compact #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
+ <textarea class="field_text oe_compact" placeholder="Write a reply..."/>
+ </div>
+ <span t-if="!(widget.show_compact_message and !widget.show_composer) and !widget.show_composer" class="oe_placeholder_compose"></span>
</t>
<!--
@@ -35,19 +48,20 @@
Small template to be inserted in the composition for add attachments
-->
<t t-name="mail.compose_message.add_attachment">
- <div class="oe_attachment_file">
- <div class="oe_add">
+ <span class="oe_attachment_file">
+ <span class="oe_add">
<!-- uploader of file -->
- <button><span class="oe_e">p</span></button>
+ <button class="oe_attach"><span class="oe_e">'</span></button>
+ <span class='oe_attach_label'>File</span>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
- <input type="hidden" name="model" value="mail.message"/>
+ <input type="hidden" name="model" value="mail.compose.message"/>
<input type="hidden" name="id" value="0"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
</t>
- </div>
- </div>
+ </span>
+ </span>
</t>
<!--
@@ -55,25 +69,33 @@
Template used to display attachments in a mail.message
-->
<t t-name="mail.thread.message.attachments">
- <ul t-attf-class="oe_msg_attachments #{widget.datasets.attachment_ids[0] and widget.options.thread.show_attachment_link?'':'oe_hidden'}">
- <t t-foreach="widget.datasets.attachment_ids" t-as="attachment">
- <li>
- <span t-if="(attachment.upload or attachment.percent_loaded&lt;100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
+ <span class="oe_msg_attachments">
+ <t t-foreach="widget.attachment_ids" t-as="attachment" t-if="!attachment.is_image">
+ <div class="oe_attachment">
+ <span t-if="(attachment.upload and attachment.percent_loaded&lt;100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
<div class="oe_upload_in_process">
<span>...Upload in progress...</span>
</div>
<t t-raw="attachment.name || attachment.filename"/>
</span>
- <a t-if="(!attachment.upload or attachment.percent_loaded&gt;=100)" t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
- <t t-raw="attachment.name || attachment.filename"/>
- </a>
- <t t-if="widget.options.thread.show_attachment_delete and (!attachment.upload or attachment.percent_loaded&gt;=100)">
- <a class="oe_right oe_mail_attachment_delete" title="Delete this attachment" t-attf-data-id="{attachment.id}">x</a>
+ <t t-if="(!attachment.upload or attachment.percent_loaded&gt;=100)">
+ <a t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
+ <t t-raw="attachment.name || attachment.filename"/>
+ </a>
</t>
-
- </li>
+ <t t-if="(widget.show_delete_attachment and (!attachment.upload or attachment.percent_loaded&gt;=100))">
+ <a class="oe_right oe_mail_attachment_delete oe_e" title="Delete this attachment" t-attf-data-id="{attachment.id}">[</a>
+ </t>
+ </div>
+ </t>
+ </span>
+ <span class="oe_msg_images">
+ <t t-foreach="widget.attachment_ids" t-as="attachment" t-if="attachment.is_image">
+ <a t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
+ <img t-if="attachment.is_image" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-att-src="attachment.url"/>
+ </a>
</t>
- </ul>
+ </span>
</t>
<t t-name="mail.thread.message.private">
@@ -89,14 +111,14 @@
-->
<t t-name="mail.thread.list_recipients">
<div class="oe_mail_list_recipients">
- Post to:
- <span t-if="!widget.datasets.is_private" class="oe_all_follower">All Followers</span>
- <t t-if="!widget.datasets.is_private and widget.datasets.partner_ids.length"> and </t>
+ To:
+ <span t-if="!widget.is_private" class="oe_all_follower">Everyone</span>
+ <t t-if="!widget.is_private and widget.partner_ids.length"> and </t>
<t t-set="inc" t-value="0"/>
- <t t-if="widget.datasets.partner_ids.length" t-foreach="widget.datasets.partner_ids" t-as="partner"><span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/><a t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a></span><t t-set="inc" t-value="inc+1"/>
+ <t t-if="widget.partner_ids.length" t-foreach="widget.partner_ids" t-as="partner"><span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/><a t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a></span><t t-set="inc" t-value="inc+1"/>
</t>
- <t t-if="widget.datasets.partner_ids.length>=3">
- <span class="oe_more">, <a><t t-raw="widget.datasets.partner_ids.length-3"/> others...</a></span>
+ <t t-if="widget.partner_ids.length>=3">
+ <span class="oe_more">, <a><t t-raw="widget.partner_ids.length-3"/> others...</a></span>
<a class="oe_more_hidden">&lt;&lt;&lt;</a>
</t>
</div>
@@ -131,122 +153,95 @@
<button type="button" class="oe_write_full oe_highlight">
Compose a new message
</button>
- <button type="button" class="oe_write_onwall" help="Your followers can read this message">
- Write to your followers
- </button>
+ <span class='oe_alternative'>
+ or
+ <a href='#' class='oe_write_onwall oe_bold' help='Your followers can read this message'>Write to your followers</a>
+ </span>
</td>
</tr>
</tbody>
</table>
- <!-- placeholder for the wall threads -->
- <div class="oe_mail_wall_threads"/>
- <div class="oe_mail_wall_aside">
- <!-- contains currently nothing -->
- </div>
+ <div class="oe_mail-placeholder"></div>
</div>
<!--
display message on the wall when there are no message
-->
- <li t-name="mail.wall_no_message" class="oe_wall_no_message">
- You have no messages
- </li>
+ <t t-name="mail.wall_no_message">
+ <div class="oe_wall_no_message">You have no messages</div>
+ </t>
<!--
record_thread main template
Template used to display the communication history in documents
form view.
-->
- <div t-name="mail.record_thread" class="oe_mail_record_wall">
- <!-- <h4>History and Comments</h4> -->
- <ul class="oe_mail_wall_threads">
- <!-- contains the document thread -->
- </ul>
- </div>
-
- <!--
- thread template
- This template holds a thread of comments. It begins with an actions
- container, holding the composition form. Then come the various
- messages. Then comes the 'more' button.
- -->
- <div t-name="mail.thread" class="oe_mail oe_mail_thread oe_semantic_html_override">
- <div class="oe_mail_thread_action">
- <!-- contains the composition box (form + image) -->
+ <div t-name="mail.record_thread" class="oe_record_thread">
+ <div class="oe_mail-placeholder">
</div>
- <ul class="oe_mail_thread_display">
- <!-- contains the threads -->
- </ul>
</div>
+ <t t-name="mail.thread">
+ <div t-attf-class="oe_thread #{widget.root?'oe_root_thread':''}"/>
+ </t>
+
<!-- default layout -->
- <li t-name="mail.thread.message" t-attf-class="oe_mail oe_mail_thread_msg #{widget.datasets.to_read ?'oe_mail_unread':'oe_mail_read'}">
- <div t-attf-class="oe_msg_#{widget.datasets.type} oe_semantic_html_override">
- <!-- message actions (read/unread, reply, delete...) -->
- <ul class="oe_header">
- <li class="placeholder-mail-vote"><t t-call="mail.thread.message.vote"/></li>
- <li class="placeholder-mail-star"><t t-call="mail.thread.message.star"/></li>
- <li t-if="widget.datasets.show_read_unread" title="Read" class="oe_read"><a class="oe_read oe_e">W</a></li>
- <li t-if="widget.datasets.show_read_unread" title="Set back to unread" class="oe_unread"><a class="oe_unread oe_e">h</a></li>
- <li title="Quick reply" t-if="widget.datasets.show_reply"><a class="oe_reply oe_e">)</a></li>
- <t t-if="(widget.datasets.is_author and widget.options.message.show_dd_delete) or widget.datasets.type == 'email'">
- <li>
- <span class="oe_dropdown_toggle">
- <a class="oe_e" title="More options">í</a>
- <ul class="oe_dropdown_menu">
- <li t-if="widget.datasets.is_author and widget.options.message.show_dd_delete"><a class="oe_msg_delete">Delete</a></li>
- <li t-if="widget.datasets.type == 'email'"><a class="oe_msg_details" t-attf-href="#model=mail.message&amp;id=#{widget.datasets.id}" >Details</a></li>
- </ul>
- </span>
- </li>
- </t>
- </ul>
+ <t t-name="mail.thread.message">
+ <div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_#{widget.type} oe_msg_#{widget.to_read?'unread':'read'}">
- <a t-attf-href="#model=res.partner&amp;id=#{widget.datasets.author_id[0]}" t-att-title="widget.datasets.author_id[1]">
- <img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="widget.datasets.avatar"/>
- </a>
+ <div class='oe_msg_left'>
+ <a t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
+ <img class="oe_msg_icon" t-att-src="widget.avatar"/>
+ </a>
+ </div>
- <div class="oe_msg_content">
+ <div class="oe_msg_center">
+ <div class='oe_msg_icons'>
+ <span class='oe_read' t-if="widget.show_read_unread_button"><a title="Read" class="oe_e">X</a></span>
+ <span class='oe_unread' t-if="widget.show_read_unread_button"><a title="Set back to unread" class="oe_e">v</a></span>
+ <span class='oe_reply' t-if="widget.show_reply_button"><a title="Reply" class="oe_e">(</a></span>
+ <span t-attf-class="oe_star #{widget.is_favorite?'oe_starred':''}"><a title="Add To Favorites" class="oe_e">7</a></span>
+ </div>
<!-- message itself -->
- <div class="oe_mail_msg">
- <h1 t-if="widget.datasets.subject" class="oe_msg_title">
- <t t-raw="widget.datasets.subject"/>
+ <div class="oe_msg_content">
+ <h1 t-if="widget.subject and !widget.thread_level" class="oe_msg_title">
+ <t t-raw="widget.subject"/>
</h1>
- <ul class="oe_msg_footer">
- <li t-if="widget.datasets.author_id"><a t-attf-href="#model=res.partner&amp;id=#{widget.datasets.author_id[0]}"><t t-raw="widget.datasets.author_id[1]"/></a></li>
- <li><span t-att-title="widget.datasets.date"><t t-raw="widget.datasets.timerelative"/></span></li>
- <li t-if="widget.datasets.attachment_ids.length > 0">
- <a class="oe_msg_view_attachments">
- <t t-if="widget.datasets.attachment_ids.length == 1">1 Attachment</t>
- <t t-if="widget.datasets.attachment_ids.length > 1"><t t-raw="widget.datasets.attachment_ids.length"/> Attachments</t>
- </a>
- </li>
- </ul>
- <div class="oe_clear"/>
<div class="oe_msg_body">
- <t t-if="widget.options.message.show_record_name and widget.datasets.record_name and (!widget.datasets.subject) and !widget.options.thread.thread_level and !widget.options.thread.display_on_thread[0] and widget.datasets.model!='res.partner'">
- <a class="oe_mail_action_model" t-attf-href="#model=#{widget.datasets.model}&amp;id=#{widget.res_id}"><t t-raw="widget.datasets.record_name"/></a>
+ <t t-if="widget.options.show_record_name and widget.record_name and (!widget.subject) and !widget.options.thread_level and !widget.options.display_on_thread[0] and widget.model!='res.partner'">
+ <a class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}"><t t-raw="widget.record_name"/></a>
</t>
- <t t-raw="widget.datasets.body"/>
+ <t t-raw="widget.body"/>
</div>
- <t t-if="widget.datasets.attachment_ids.length > 0">
- <div class="oe_clear"></div>
+ <t t-if="widget.attachment_ids.length > 0">
<t t-call="mail.thread.message.attachments"/>
</t>
</div>
+ <div class="oe_msg_footer">
+ <a t-if="widget.author_id" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
+ <span class='oe_subtle'>•</span>
+ <span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span>
+ <a t-if="widget.attachment_ids.length > 0" class="oe_mail_msg_view_attachments">
+ <t t-if="widget.attachment_ids.length == 1">1 Attachment</t>
+ <t t-if="widget.attachment_ids.length > 1"><t t-raw="widget.attachment_ids.length"/> Attachments</t>
+ </a>
+ <span class='oe_subtle'>•</span>
+ <t t-call="mail.thread.message.vote"/>
+ </div>
</div>
</div>
- <div class="oe_thread_placeholder"></div>
- </li>
+ </t>
<!-- expandable message layout -->
- <li t-name="mail.thread.expandable" class="oe_mail oe_mail_thread_msg oe_mail_unread">
- <div t-attf-class="oe_msg_#{widget.datasets.type} oe_semantic_html_override">
+ <t t-name="mail.thread.expandable">
+ <div t-attf-class="oe_msg oe_msg_#{widget.type} #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_unread">
<div class="oe_msg_content oe_msg_more_message">
- <a class="oe_mail_fetch_more">Load more messages <span t-if="widget.datasets.nb_messages>0">(<t t-raw="widget.datasets.nb_messages"/> messages not display)</span>...</a>
+ <div class='oe_separator'></div>
+ <a t-if="widget.nb_messages === 1" class="oe_msg_fetch_more">show one more message</a>
+ <a t-if="widget.nb_messages !== 1" class="oe_msg_fetch_more">show <t t-raw="widget.nb_messages" /> more messages</a>
</div>
</div>
- </li>
+ </t>
<!--
mail.compose_message.button_top_bar
@@ -254,7 +249,7 @@
-->
<t t-name="mail.compose_message.button_top_bar">
<div class="oe_topbar_compose_full_email">
- <button class="oe_button oe_highlight">Write an email</button>
+ <!-- <button class="oe_button oe_highlight">Write an email</button> -->
</div>
</t>
@@ -262,33 +257,18 @@
Template used to display Like/Unlike in a mail.message
-->
<span t-name="mail.thread.message.vote">
- <span class="oe_left oe_mail_vote_count">
- <t t-if='widget.datasets.has_voted'>
- You
- </t>
- <t t-if='(widget.datasets.vote_user_ids.length-(widget.datasets.has_voted?1:0)) > 0'>
- <t t-if='widget.datasets.has_voted'> and </t>
- <t t-esc="widget.datasets.vote_user_ids.length"/> people
- </t>
- <t t-if='widget.datasets.vote_user_ids.length > 0'>
- agree
- </t>
+
+ <span class="oe_mail_vote_count" t-if='widget.vote_nb > 0'>
+ <t t-raw='widget.vote_nb' />
+ <span class='oe_e'>8</span>
</span>
- <button t-attf-class="oe_msg_vote oe_tag">
- <span>
- <t t-if="!widget.has_voted">Agree</t>
- <t t-if="widget.has_voted">Undo</t>
- </span>
- </button>
+ <a href='#' t-attf-class="oe_msg_vote">
+ <t t-if="!widget.has_voted">like</t>
+ <t t-if="widget.has_voted">unlike</t>
+ </a>
</span>
<!-- mail.thread.message.star
Template used to display stared/unstared message in a mail.message
-->
- <span t-name="mail.thread.message.star">
- <span class="oe_left">
- <button t-attf-class="oe_mail_starbox oe_tag #{widget.datasets.is_favorite?'oe_stared':''}">*</button>
- </span>
- </span>
-
</template>
diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml
index 87b49579100..3dec4ef1dd2 100644
--- a/addons/mail/static/src/xml/mail_followers.xml
+++ b/addons/mail/static/src/xml/mail_followers.xml
@@ -5,51 +5,45 @@
followers main template
Template used to display the followers, the actions and the subtypes in a record.
-->
- <div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
- <div class="oe_mail_recthread_actions">
- <div class="oe_mail_subtypes">
- <button type="button" class="oe_follower oe_notfollow">
- <span class="oe_follow">Follow</span>
- <span class="oe_unfollow">Unfollow</span>
- <span class="oe_following">Following</span>
- </button>
- </div>
- <div class="oe_recthread_subtypes">
- <ul class="oe_subtypes"></ul>
- </div>
+ <div t-name="mail.followers" class="oe_followers">
+ <div class="oe_actions">
+ <button type="button" class="oe_follower oe_notfollow">
+ <span class="oe_follow">Follow</span>
+ <span class="oe_unfollow">Unfollow</span>
+ <span class="oe_following">Following</span>
+ </button>
+ <div class="oe_subtype_list"></div>
</div>
- <t t-if="widget.options.comment">
- <h5 class="oe_grey"><t t-raw="widget.options.comment"/></h5>
+ <t t-if="widget.comment">
+ <h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
</t>
- <div class="oe_mail_recthread_followers">
- <button type="button" class="oe_invite"><span>Invite</span></button>
- <t t-if="widget.options.title">
- <h4><t t-raw="widget.options.title"/></h4>
- </t>
- <ul class="oe_mail_followers_display"></ul>
+ <div class='oe_follower_title_box'>
+ <h4 class='oe_follower_title'>Followers</h4>
+ <a href='#' class="oe_invite">Invite others</a>
</div>
+ <div class="oe_follower_list"></div>
</div>
<!--
followers.partner template
Template used to display a partner following the record
-->
- <li t-name="mail.followers.partner">
+ <div t-name="mail.followers.partner" class='oe_partner'>
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.partner&amp;id=#{record.id}"><t t-raw="record.name"/></a>
- </li>\
+ </div>
<!--
followers.subtype template
Template used to display message subtypes of a follower subscription
-->
- <li t-name="mail.followers.subtype">
- <table width="50%">
+ <t t-name="mail.followers.subtype">
+ <table class='oe_subtype'>
<tr>
- <td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
<td width="10%"><input type="checkbox" t-att-checked="record.followed" t-att-id="'input_mail_followers_subtype_'+record.id" t-att-data-id="record.id" t-att-name="record.name" class="oe_msg_subtype_check"/></td>
+ <td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
</tr>
</table>
- </li>
+ </t>
</template>
diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py
index 2eff59fe0ed..85cfdca578c 100644
--- a/addons/mail/tests/test_mail.py
+++ b/addons/mail/tests/test_mail.py
@@ -94,6 +94,8 @@ class TestMailMockups(common.TransactionCase):
self._build_email_kwargs_list = []
def _mock_build_email(self, *args, **kwargs):
+ """ Mock build_email to be able to test its values. Store them into
+ some internal variable for latter processing. """
self._build_email_args_list.append(args)
self._build_email_kwargs_list.append(kwargs)
return self._build_email(*args, **kwargs)
@@ -123,6 +125,7 @@ class test_mail(TestMailMockups):
def setUp(self):
super(test_mail, self).setUp()
+ cr, uid = self.cr, self.uid
self.ir_model = self.registry('ir.model')
self.mail_alias = self.registry('mail.alias')
self.mail_thread = self.registry('mail.thread')
@@ -135,22 +138,27 @@ class test_mail(TestMailMockups):
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
+ # Find Employee group
+ group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
+ group_employee_id = group_employee_ref and group_employee_ref[1] or False
# Test users
- self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
- self.user_admin = self.res_users.browse(self.cr, self.uid, self.uid)
+ self.user_raoul_id = self.res_users.create(cr, uid,
+ {'name': 'Raoul Grosbedon', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [group_employee_id])]})
+ self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
+ self.user_admin = self.res_users.browse(cr, uid, uid)
# Mock send_get_mail_body to test its functionality without other addons override
self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
# groups@.. will cause the creation of new mail groups
- self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
- self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
+ self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
+ self.mail_alias.create(cr, uid, {'alias_name': 'groups',
'alias_model_id': self.mail_group_model_id})
# create a 'pigs' group that will be used through the various tests
- self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
+ self.group_pigs_id = self.mail_group.create(cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
- self.group_pigs = self.mail_group.browse(self.cr, self.uid, self.group_pigs_id)
+ self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def tearDown(self):
# Remove mocks
@@ -159,7 +167,7 @@ class test_mail(TestMailMockups):
def test_00_message_process(self):
""" Testing incoming emails processing. """
- cr, uid = self.cr, self.uid
+ cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
# Incoming mail creates a new mail_group "frogs"
self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
@@ -197,6 +205,26 @@ class test_mail(TestMailMockups):
self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
'plaintext mail incorrectly parsed')
+ # Do: post a new message, with a known partner
+ test_msg_id = '<deadcafe.1337-2@smtp.agrolait.com>'
+ TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', user_raoul.email)
+ mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='extra news', extra='', msg_id=test_msg_id)
+ self.mail_thread.message_process(cr, uid, None, mail_new)
+ new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
+ # Test: author_id set, not email_from
+ self.assertEqual(new_mail.author_id, user_raoul.partner_id, 'message process wrong author found')
+ self.assertFalse(new_mail.email_from, 'message process should not set the email_from when an author is found')
+
+ # Do: post a new message, with a unknown partner
+ test_msg_id = '<deadcafe.1337-3@smtp.agrolait.com>'
+ TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', '_abcd_')
+ mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='super news', extra='', msg_id=test_msg_id)
+ self.mail_thread.message_process(cr, uid, None, mail_new)
+ new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
+ # Test: author_id set, not email_from
+ self.assertFalse(new_mail.author_id, 'message process shnould not have found a partner for _abcd_ email address')
+ self.assertIn('_abcd_', new_mail.email_from, 'message process should set en email_from when not finding a partner_id')
+
def test_10_followers_function_field(self):
""" Tests designed for the many2many function field 'follower_ids'.
We will test to perform writes using the many2many commands 0, 3, 4,
@@ -260,7 +288,7 @@ class test_mail(TestMailMockups):
""" Tests designed for the subscriber API as well as message subtypes """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
# Data: user Raoul
- user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
+ user_raoul_id = self.user_raoul_id
user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
# Data: message subtypes
self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
@@ -311,7 +339,25 @@ class test_mail(TestMailMockups):
self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
- def test_20_message_post(self):
+ def test_20_message_quote_context(self):
+ """ Tests designed for message_post. """
+ cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
+
+ msg1_id = self.mail_message.create(cr, uid, {'body': 'Thread header about Zap Brannigan', 'subject': 'My subject'})
+ msg2_id = self.mail_message.create(cr, uid, {'body': 'First answer, should not be displayed', 'subject': 'Re: My subject', 'parent_id': msg1_id})
+ msg3_id = self.mail_message.create(cr, uid, {'body': 'Second answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
+ msg4_id = self.mail_message.create(cr, uid, {'body': 'Third answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
+ msg_new_id = self.mail_message.create(cr, uid, {'body': 'My answer I am propagating', 'subject': 'Re: My subject', 'parent_id': msg1_id})
+
+ result = self.mail_message.message_quote_context(cr, uid, msg_new_id, limit=3)
+ self.assertIn('Thread header about Zap Brannigan', result, 'Thread header content should be in quote.')
+ self.assertIn('Second answer', result, 'Answer should be in quote.')
+ self.assertIn('Third answer', result, 'Answer should be in quote.')
+ self.assertIn('expandable', result, 'Expandable should be present.')
+ self.assertNotIn('First answer, should not be displayed', result, 'Old answer should not be in quote.')
+ self.assertNotIn('My answer I am propagating', result, 'Thread header content should be in quote.')
+
+ def test_21_message_post(self):
""" Tests designed for message_post. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
@@ -357,11 +403,11 @@ class test_mail(TestMailMockups):
# the html2plaintext uses etree or beautiful soup, so the result may be slighly different
# depending if you have installed beautiful soup.
self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect')
- # Test: mail_message: partner_ids = group followers
- message_pids = set([partner.id for partner in message.partner_ids])
+ # Test: mail_message: notified_partner_ids = group followers
+ message_pids = set([partner.id for partner in message.notified_partner_ids])
test_pids = set([p_b_id, p_c_id])
self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
- # Test: notification linked to this message = group followers = partner_ids
+ # Test: notification linked to this message = group followers = notified_partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
@@ -390,11 +436,11 @@ class test_mail(TestMailMockups):
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
- # Test: mail_message: partner_ids = group followers
- message_pids = set([partner.id for partner in message.partner_ids])
+ # Test: mail_message: notified_partner_ids = group followers
+ message_pids = set([partner.id for partner in message.notified_partner_ids])
test_pids = set([p_b_id, p_c_id, p_d_id])
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
- # Test: notifications linked to this message = group followers = partner_ids
+ # Test: notifications linked to this message = group followers = notified_partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
@@ -408,6 +454,11 @@ class test_mail(TestMailMockups):
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
'mail.message attachment name / data incorrect')
+ # 3. Reply to the last message, check that its parent will be the first message
+ msg_id3 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Test', parent_id=msg_id2)
+ message = self.mail_message.browse(cr, uid, msg_id3)
+ self.assertEqual(message.parent_id.id, msg_id, 'message_post did not flatten the thread structure')
+
def test_25_message_compose_wizard(self):
""" Tests designed for the mail.compose.message wizard. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
@@ -458,12 +509,12 @@ class test_mail(TestMailMockups):
# Test: mail.message: subject, body inside pre
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
- # Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
- msg_pids = [partner.id for partner in message.partner_ids]
+ # Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
+ msg_pids = [partner.id for partner in message.notified_partner_ids]
test_pids = [p_b_id, p_c_id, p_d_id]
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
self.assertEqual(len(notif_ids), 3, 'mail.message: too much notifications created')
- self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
+ self.assertEqual(set(msg_pids), set(test_pids), 'mail.message notified_partner_ids incorrect')
# ----------------------------------------
# CASE2: reply to last comment with attachments
@@ -482,7 +533,7 @@ class test_mail(TestMailMockups):
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# Test: mail.message: subject as Re:.., body in html, parent_id
self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
- self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
+ # self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
# Test: mail.message: attachments
for attach in compose.attachment_ids:
@@ -522,15 +573,158 @@ class test_mail(TestMailMockups):
def test_30_message_read(self):
""" Tests for message_read and expandables. """
- self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
+ cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
+ pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
+
+ # Data: create a discussion in Pigs (2 messages, one with 2 and one with 3 answers)
+ msg_id0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
+ msg_id1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
+ msg_id2 = self.group_pigs.message_post(body='2', subtype='mt_comment')
+ msg_id3 = self.group_pigs.message_post(body='1-1', subtype='mt_comment', parent_id=msg_id1)
+ msg_id4 = self.group_pigs.message_post(body='2-1', subtype='mt_comment', parent_id=msg_id2)
+ msg_id5 = self.group_pigs.message_post(body='1-2', subtype='mt_comment', parent_id=msg_id1)
+ msg_id6 = self.group_pigs.message_post(body='2-2', subtype='mt_comment', parent_id=msg_id2)
+ msg_id7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
+ msg_id8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
+ msg_id9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
+ msg_id10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
+ msg_ids = [msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8, msg_id9, msg_id10]
+
+ # Test: read some specific ids
+ read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
+ read_msg_ids = [msg.get('id') for msg in read_msg_list]
+ self.assertEqual(msg_ids[2:4], read_msg_ids, 'message_read with direct ids should read only the requested ids')
+
+ # Test: read messages of Pigs through a domain, being thread or not threaded
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list]
+ self.assertEqual(msg_ids, read_msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200, thread_level=1)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list]
+ self.assertEqual(msg_ids, read_msg_ids, 'message_read threaded with domain on Pigs should equal all messages of Pigs')
+
+ # ----------------------------------------
+ # CASE1: message_read with domain, threaded
+ # We simulate an entire flow, using the expandables to test them
+ # ----------------------------------------
+
+ # Do: read last message, threaded
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
+ self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
+ self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
+ self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
+ # Data: get expandables
+ new_threads_exp, new_msg_exp = None, None
+ for msg in read_msg_list:
+ if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
+ new_threads_exp = msg
+ elif msg.get('type') == 'expandable':
+ new_msg_exp = msg
+
+ # Do: fetch new messages in first thread, domain from expandable
+ self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable')
+ domain = new_msg_exp.get('domain', [])
+ # Test: expandable, conditions in domain
+ self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition')
+ self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition')
+ self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition')
+ self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have ancestor_id set to the thread header')
+ # Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS)
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id2)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: other message in thread have been fetch
+ self.assertEqual(set([msg_id4, msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return all the previous messages')
+
+ # Do: fetch a new thread, domain from expandable
+ self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
+ domain = new_threads_exp.get('domain', [])
+ # Test: expandable, conditions in domain
+ for condition in pigs_domain:
+ self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
+ self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an ancestor_id')
+ # Do: message_read with domain, thread_level=1 (should be imposed by JS)
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
+ self.assertEqual(len(read_msg_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
+ self.assertEqual(set([msg_id1, msg_id9]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent')
+ self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
+ # Data: get expandables
+ new_threads_exp, new_msg_exp = None, None
+ for msg in read_msg_list:
+ if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
+ new_threads_exp = msg
+ elif msg.get('type') == 'expandable':
+ new_msg_exp = msg
+
+ # Do: fetch new messages in second thread, domain from expandable
+ self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable')
+ domain = new_msg_exp.get('domain', [])
+ # Test: expandable, conditions in domain
+ self.assertIn(('id', 'child_of', msg_id1), domain, 'new messages expandable domain should contain a child_of condition')
+ self.assertIn(('id', '>=', msg_id3), domain, 'new messages expandable domain should contain an id greater than condition')
+ self.assertIn(('id', '<=', msg_id7), domain, 'new messages expandable domain should contain an id less than condition')
+ self.assertEqual(new_msg_exp.get('parent_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header')
+ # Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS)
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id1)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: other message in thread have been fetch
+ self.assertEqual(set([msg_id3, msg_id5, msg_id7]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
+
+ # Test: fetch a new thread, domain from expandable
+ self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable')
+ domain = new_threads_exp.get('domain', [])
+ # Test: expandable, conditions in domain
+ for condition in pigs_domain:
+ self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter')
+ # Do: message_read with domain, thread_level=1 (should be imposed by JS)
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
+ self.assertEqual(len(read_msg_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
+ self.assertEqual([msg_id0], read_msg_ids, 'message_read after 2 More should return only 1 last message')
+
+ # ----------------------------------------
+ # CASE2: message_read with domain, flat
+ # ----------------------------------------
+
+ # Do: read 2 lasts message, flat
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=2, thread_level=0)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable
+ self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
+ self.assertEqual(set([msg_id9, msg_id10]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages')
+ self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False')
+ self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False')
+ # Data: get expandables
+ new_threads_exp, new_msg_exp = None, None
+ for msg in read_msg_list:
+ if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
+ new_threads_exp = msg
+
+ # Do: fetch new messages, domain from expandable
+ self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
+ domain = new_threads_exp.get('domain', [])
+ # Test: expandable, conditions in domain
+ for condition in pigs_domain:
+ self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
+ # Do: message_read with domain, thread_level=0 (should be imposed by JS)
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=20, thread_level=0)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
+ self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
+ self.assertEqual([msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8], read_msg_ids,
+ 'message_read, More on flat, should return all remaning messages')
def test_40_needaction(self):
""" Tests for mail.message needaction. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
- user_demo = self.res_users.browse(cr, uid, self.user_demo_id)
- group_pigs_demo = self.mail_group.browse(cr, self.user_demo_id, self.group_pigs_id)
+ user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
+ group_pigs_demo = self.mail_group.browse(cr, self.user_raoul_id, self.group_pigs_id)
na_admin_base = self.mail_message._needaction_count(cr, uid, domain=[])
- na_demo_base = self.mail_message._needaction_count(cr, user_demo.id, domain=[])
+ na_demo_base = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
# Test: number of unread notification = needaction on mail.message
notif_ids = self.mail_notification.search(cr, uid, [
@@ -558,12 +752,12 @@ class test_mail(TestMailMockups):
self.assertEqual(na_admin_group, 3, 'Admin should have 3 needaction related to Pigs')
# Test: demo has 0 new notifications (not a follower, not receiving its own messages), and 0 new needaction
notif_ids = self.mail_notification.search(cr, uid, [
- ('partner_id', '=', user_demo.partner_id.id),
+ ('partner_id', '=', user_raoul.partner_id.id),
('read', '=', False)
])
self.assertEqual(len(notif_ids), na_demo_base + 0, 'Demo should have 0 new unread notifications')
- na_demo = self.mail_message._needaction_count(cr, user_demo.id, domain=[])
- na_demo_group = self.mail_message._needaction_count(cr, user_demo.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
+ na_demo = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
+ na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
@@ -581,19 +775,11 @@ class test_mail(TestMailMockups):
reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
extra='In-Reply-To: %s' % msg1.message_id)
self.mail_group.message_process(cr, uid, None, reply_msg)
- # TDE note: temp various asserts because of the random bug about msg1.child_ids
- msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=1)
- new_msg = self.mail_message.browse(cr, uid, msg_ids[0])
- self.assertEqual(new_msg.parent_id, msg1, 'Newly processed mail_message (%d) should have msg1 as parent' % (new_msg.id))
# 2. References header
reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
self.mail_group.message_process(cr, uid, None, reply_msg2)
- # TDE note: temp various asserts because of the random bug about msg1.child_ids
- msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=1)
- new_msg = self.mail_message.browse(cr, uid, msg_ids[0])
- self.assertEqual(new_msg.parent_id, msg1, 'Newly processed mail_message should have msg1 as parent')
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
@@ -613,34 +799,46 @@ class test_mail(TestMailMockups):
def test_60_message_vote(self):
""" Test designed for the vote/unvote feature. """
- cr, uid = self.cr, self.uid
- user_admin = self.res_users.browse(cr, uid, uid)
- group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
- msg1 = group_pigs.message_post(body='My Body', subject='1')
- msg1 = self.mail_message.browse(cr, uid, msg1)
-
- # Create user Bert Tartopoils
- user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert', 'login': 'bert'})
- user_bert = self.res_users.browse(cr, uid, user_bert_id)
-
- # Test: msg1 and msg2 have void vote_user_ids
- self.assertFalse(msg1.vote_user_ids, 'newly created message msg1 has not void vote_user_ids')
- # Do: Admin vote for msg1
- self.mail_message.vote_toggle(cr, uid, [msg1.id])
- msg1.refresh()
- # Test: msg1 has Admin as voter
- self.assertEqual(set(msg1.vote_user_ids), set([user_admin]), 'after voting, Admin is not the voter')
- # Do: Bert vote for msg1
- self.mail_message.vote_toggle(cr, user_bert_id, [msg1.id])
- msg1.refresh()
- # Test: msg1 has Admin and Bert as voters
- self.assertEqual(set(msg1.vote_user_ids), set([user_admin, user_bert]), 'after voting, Admin and Bert are not the voters')
- # Do: Admin unvote for msg1
- self.mail_message.vote_toggle(cr, uid, [msg1.id])
- msg1.refresh()
- # Test: msg1 has Bert as voter
- self.assertEqual(set(msg1.vote_user_ids), set([user_bert]), 'after unvoting for Admin, Bert is not the voter')
+ cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
+ # Data: post a message on Pigs
+ msg_id = group_pigs.message_post(body='My Body', subject='1')
+ msg = self.mail_message.browse(cr, uid, msg_id)
+
+ # Do: Admin vote for msg
+ self.mail_message.vote_toggle(cr, uid, [msg.id])
+ msg.refresh()
+ # Test: msg has Admin as voter
+ self.assertEqual(set(msg.vote_user_ids), set([user_admin]), 'mail_message vote: after voting, Admin should be in the voter')
+ # Do: Bert vote for msg
+ self.mail_message.vote_toggle(cr, user_raoul.id, [msg.id])
+ msg.refresh()
+ # Test: msg has Admin and Bert as voters
+ self.assertEqual(set(msg.vote_user_ids), set([user_admin, user_raoul]), 'mail_message vote: after voting, Admin and Bert should be in the voters')
+ # Do: Admin unvote for msg
+ self.mail_message.vote_toggle(cr, uid, [msg.id])
+ msg.refresh()
+ # Test: msg has Bert as voter
+ self.assertEqual(set(msg.vote_user_ids), set([user_raoul]), 'mail_message vote: after unvoting, Bert should be in the voter')
def test_70_message_favorite(self):
""" Tests for favorites. """
- self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
+ cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
+ # Data: post a message on Pigs
+ msg_id = group_pigs.message_post(body='My Body', subject='1')
+ msg = self.mail_message.browse(cr, uid, msg_id)
+
+ # Do: Admin stars msg
+ self.mail_message.favorite_toggle(cr, uid, [msg.id])
+ msg.refresh()
+ # Test: msg starred by Admin
+ self.assertEqual(set(msg.favorite_user_ids), set([user_admin]), 'mail_message favorite: after starring, Admin should be in favorite_user_ids')
+ # Do: Bert stars msg
+ self.mail_message.favorite_toggle(cr, user_raoul.id, [msg.id])
+ msg.refresh()
+ # Test: msg starred by Admin and Raoul
+ self.assertEqual(set(msg.favorite_user_ids), set([user_admin, user_raoul]), 'mail_message favorite: after starring, Admin and Raoul should be in favorite_user_ids')
+ # Do: Admin unvote for msg
+ self.mail_message.favorite_toggle(cr, uid, [msg.id])
+ msg.refresh()
+ # Test: msg starred by Raoul
+ self.assertEqual(set(msg.favorite_user_ids), set([user_raoul]), 'mail_message favorite: after unstarring, Raoul should be in favorite_user_ids')
diff --git a/addons/mail/tests/test_mail_access_rights.py b/addons/mail/tests/test_mail_access_rights.py
index 544191205b8..7b90e07b9b5 100644
--- a/addons/mail/tests/test_mail_access_rights.py
+++ b/addons/mail/tests/test_mail_access_rights.py
@@ -51,7 +51,40 @@ class test_mail_access_rights(test_mail.TestMailMockups):
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.partner_raoul_id = self.user_raoul.partner_id.id
- def test_00_mail_message_read_access_rights(self):
+ def test_00_mail_message_search_access_rights(self):
+ """ Test mail_message search override about access rights. """
+ cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
+ partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
+ user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
+ # Data: comment subtype for mail.message creation
+ ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'mail', 'mt_comment')
+ subtype_id = ref and ref[1] or False
+
+ # Data: Birds group, private
+ group_birds_id = self.mail_group.create(self.cr, self.uid, {'name': 'Birds', 'public': 'private'})
+ # Data: raoul is member of Pigs
+ self.mail_group.message_subscribe(cr, uid, [group_pigs_id], [partner_raoul_id])
+ # Data: various author_ids, partner_ids, documents
+ msg_id1 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A', 'subtype_id': subtype_id})
+ msg_id2 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B', 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
+ msg_id3 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'subtype_id': subtype_id})
+ msg_id4 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
+ msg_id5 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+R Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
+ msg_id6 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Birds', 'model': 'mail.group', 'res_id': group_birds_id, 'subtype_id': subtype_id})
+ msg_id7 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B', 'subtype_id': subtype_id})
+ msg_id8 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B+R', 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
+
+ # Test: Bert: 2 messages that have Bert in partner_ids + 2 messages as author
+ msg_ids = self.mail_message.search(cr, user_bert_id, [('subject', 'like', '_Test')])
+ self.assertEqual(set([msg_id2, msg_id4, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
+ # Test: Raoul: 3 messages on Pigs Raoul can read (employee can read group with default values), 0 on Birds (private group)
+ msg_ids = self.mail_message.search(cr, user_raoul_id, [('subject', 'like', '_Test'), ('body', 'like', 'A')])
+ self.assertEqual(set([msg_id3, msg_id4, msg_id5]), set(msg_ids), 'mail_message search failed')
+ # Test: Admin: all messages
+ msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
+ self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
+
+ def test_05_mail_message_read_access_rights(self):
""" Test basic mail_message read access rights. """
cr, uid = self.cr, self.uid
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
@@ -98,10 +131,6 @@ class test_mail_access_rights(test_mail.TestMailMockups):
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
- def test_05_mail_message_search_access_rights(self):
- """ Test mail_message search override about access rights. """
- self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
-
def test_10_mail_flow_access_rights(self):
""" Test a Chatter-looks alike flow. """
cr, uid = self.cr, self.uid
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index b68aae14521..1bcbb0c2efc 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -156,12 +156,6 @@ class mail_compose_message(osv.TransientModel):
reply_subject = tools.ustr(message_data.subject or '')
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject:
reply_subject = "%s %s" % (re_prefix, reply_subject)
- # create the reply in the body
- reply_body = _('<div>On %(date)s, %(sender_name)s wrote:<blockquote>%(body)s</blockquote></div>') % {
- 'date': message_data.date if message_data.date else '',
- 'sender_name': message_data.author_id.name,
- 'body': message_data.body,
- }
# get partner_ids from original message
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
@@ -170,7 +164,6 @@ class mail_compose_message(osv.TransientModel):
'model': message_data.model,
'res_id': message_data.res_id,
'parent_id': message_data.id,
- 'body': reply_body,
'subject': reply_subject,
'partner_ids': partner_ids,
'content_subtype': 'html',
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index 255954e31c1..1f00ed419a2 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -14,22 +14,16 @@
<field name="parent_id" invisible="1"/>
<field name="content_subtype" invisible="1"/>
<!-- visible wizard -->
- <field name="subject" placeholder="Subject..."
- attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>/>
<field name="partner_ids" widget="many2many_tags" placeholder="Add contacts to notify..."
context="{'force_email':True}"
- on_change="onchange_partner_ids(partner_ids)"/>
+ on_change="onchange_partner_ids(partner_ids)" required="1"/>
+ <field name="subject" placeholder="Subject..."
+ attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
</group>
- <notebook>
- <page string="Body">
- <field name="body" nolabel="1"/>
- </page>
- <page string="Attachments">
- <field name="attachment_ids" colspan="4" nolabel="1"/>
- </page>
- </notebook>
+ <field name="body"/>
+ <field name="attachment_ids" widget="one2many_binary" blockui="0"/>
<footer>
- <button string="Send" name="send_mail" type="object" class="oe_highlight" />
+ <button string="Send" name="send_mail" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>