Browse Source

Merge pull request #258 from yelizariev/10.0-pre-commit

commit is created by 👷‍♂️ Merge Bot: https://odoo-devops.readthedocs.io/en/latest/git/github-merge-bot.html
pull/262/head
Mitchell Admin 5 years ago
committed by GitHub
parent
commit
2f04ff7893
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .DINAR/image/README.md
  2. 1
      .DINAR/image/src/addons.yaml
  3. 2
      .isort.cfg
  4. 21
      .travis.yml
  5. 24
      mail_all/__manifest__.py
  6. 1
      mail_all/models/__init__.py
  7. 93
      mail_all/static/src/js/mail_all.js
  8. 11
      mail_all/static/src/xml/menu.xml
  9. 7
      mail_all/tests/test_js.py
  10. 15
      mail_all/views/templates.xml
  11. 1
      mail_archives/__init__.py
  12. 25
      mail_archives/__manifest__.py
  13. 126
      mail_archives/static/src/js/archives.js
  14. 11
      mail_archives/static/src/xml/menu.xml
  15. 10
      mail_archives/tests/test_js.py
  16. 15
      mail_archives/views/templates.xml
  17. 21
      mail_attachment_popup/__manifest__.py
  18. 49
      mail_attachment_popup/static/src/css/jquery.arcticmodal.css
  19. 28
      mail_attachment_popup/static/src/css/simple.css
  20. 50
      mail_attachment_popup/static/src/xml/mail_attachment_popup.xml
  21. 28
      mail_attachment_popup/views/mail_attachment_popup_template.xml
  22. 18
      mail_base/__manifest__.py
  23. 1
      mail_base/controllers/__init__.py
  24. 6
      mail_base/controllers/main.py
  25. 18
      mail_base/models.py
  26. 12
      mail_base/views/templates.xml
  27. 26
      mail_check_immediately/__manifest__.py
  28. 49
      mail_check_immediately/models.py
  29. 53
      mail_check_immediately/static/src/js/main.js
  30. 14
      mail_check_immediately/static/src/xml/main.xml
  31. 11
      mail_check_immediately/views.xml
  32. 6
      mail_fix_553/__manifest__.py
  33. 2
      mail_fix_553/data.xml
  34. 157
      mail_fix_553/mail_fix_553.py
  35. 31
      mail_move_message/__manifest__.py
  36. 46
      mail_move_message/controllers/main.py
  37. 2
      mail_move_message/data/mail_move_message_data.xml
  38. 1
      mail_move_message/doc/index.rst
  39. 451
      mail_move_message/mail_move_message_models.py
  40. 170
      mail_move_message/mail_move_message_views.xml
  41. 5
      mail_move_message/static/src/css/mail_move_message.css
  42. 128
      mail_move_message/static/src/js/mail_move_message.js
  43. 10
      mail_move_message/static/src/xml/mail_move_message_main.xml
  44. 21
      mail_private/__manifest__.py
  45. 16
      mail_private/full_composer_wizard.xml
  46. 85
      mail_private/models.py
  47. 479
      mail_private/static/src/js/mail_private.js
  48. 43
      mail_private/static/src/js/test_private.js
  49. 23
      mail_private/static/src/xml/mail_private.xml
  50. 19
      mail_private/template.xml
  51. 16
      mail_private/tests/test_js.py
  52. 1
      mail_recovery/__init__.py
  53. 22
      mail_recovery/__manifest__.py
  54. 13
      mail_recovery/data.xml
  55. 15
      mail_recovery/static/src/js/mail_recovery.js
  56. 1
      mail_reply/__init__.py
  57. 18
      mail_reply/__manifest__.py
  58. 73
      mail_reply/static/src/js/mail_reply.js
  59. 14
      mail_reply/static/src/xml/reply_button.xml
  60. 15
      mail_reply/templates.xml
  61. 25
      mail_sent/__manifest__.py
  62. 30
      mail_sent/models.py
  63. 113
      mail_sent/static/src/js/sent.js
  64. 14
      mail_sent/static/src/xml/menu.xml
  65. 10
      mail_sent/tests/test_js.py
  66. 12
      mail_sent/views/templates.xml
  67. 16
      mail_to/__manifest__.py
  68. 13
      mail_to/models/mail_message.py
  69. 28
      mail_to/static/src/js/mail_to.js
  70. 18
      mail_to/static/src/js/test_mail_to.js
  71. 31
      mail_to/static/src/xml/recipient.xml
  72. 22
      mail_to/templates.xml
  73. 16
      mail_to/tests/test_default.py
  74. 18
      mailgun/__manifest__.py
  75. 16
      mailgun/controllers/main.py
  76. 2
      mailgun/data/cron.xml
  77. 3
      mailgun/doc/index.rst
  78. 35
      mailgun/models.py
  79. 22
      res_partner_company_messages/__manifest__.py
  80. 18
      res_partner_company_messages/models.py
  81. 2
      res_partner_company_messages/views.xml
  82. 16
      res_partner_mails_count/__manifest__.py
  83. 14
      res_partner_mails_count/models.py
  84. 44
      res_partner_mails_count/static/src/js/res_partner_mails_count_tour.js
  85. 35
      res_partner_mails_count/templates.xml
  86. 1
      res_partner_mails_count/tests/__init__.py
  87. 64
      res_partner_mails_count/tests/test_mail.py
  88. 17
      res_partner_mails_count/tests/test_phantom.py
  89. 2
      res_partner_mails_count/views/res_partner_mails_count.xml

6
.DINAR/image/README.md

@ -1,2 +1,4 @@
This folder is attached on image building as `custom/` folder in [doobba](https://github.com/Tecnativa/doodba#image-usage).
Few additional [files](https://github.com/itpp-labs/DINAR/tree/master/embedded-files/.DINAR/image) are attached temporary on image building.
This folder is attached on image building as `custom/` folder in
[doobba](https://github.com/Tecnativa/doodba#image-usage). Few additional
[files](https://github.com/itpp-labs/DINAR/tree/master/embedded-files/.DINAR/image) are
attached temporary on image building.

1
.DINAR/image/src/addons.yaml

@ -1,5 +1,4 @@
# see https://github.com/Tecnativa/doodba#optodoocustomsrcaddonsyaml # see https://github.com/Tecnativa/doodba#optodoocustomsrcaddonsyaml
--- ---
ENV: ENV:
DEFAULT_REPO_PATTERN: https://github.com/it-projects-llc/{}.git DEFAULT_REPO_PATTERN: https://github.com/it-projects-llc/{}.git

2
.isort.cfg

@ -9,4 +9,4 @@ line_length=88
known_odoo=odoo known_odoo=odoo
known_odoo_addons=odoo.addons known_odoo_addons=odoo.addons
sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER
known_third_party=
known_third_party=openerp,requests,simplejson

21
.travis.yml

@ -11,27 +11,28 @@ addons:
postgresql: "9.5" postgresql: "9.5"
apt: apt:
packages: packages:
- expect-dev # provides unbuffer utility
- python-lxml # because pip installation is slow
- expect-dev # provides unbuffer utility
- python-lxml # because pip installation is slow
env: env:
global: global:
- VERSION="10.0" TESTS="0" LINT_CHECK="0" UNIT_TEST="0"
- PYLINT_ODOO_JSLINTRC="/home/travis/maintainer-quality-tools/travis/cfg/.jslintrc"
- VERSION="10.0" TESTS="0" LINT_CHECK="0" UNIT_TEST="0"
- PYLINT_ODOO_JSLINTRC="/home/travis/maintainer-quality-tools/travis/cfg/.jslintrc"
matrix: matrix:
- LINT_CHECK="1"
- CHECK_TAGS="1"
- TESTS="1" ODOO_REPO="odoo/odoo"
- MAKEPOT="1"
- TESTS="1" ODOO_REPO="OCA/OCB"
- LINT_CHECK="1"
- CHECK_TAGS="1"
- TESTS="1" ODOO_REPO="odoo/odoo"
- MAKEPOT="1"
- TESTS="1" ODOO_REPO="OCA/OCB"
virtualenv: virtualenv:
system_site_packages: true system_site_packages: true
install: install:
- pip install anybox.testing.openerp - pip install anybox.testing.openerp
- git clone https://github.com/it-projects-llc/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
- git clone https://github.com/it-projects-llc/maintainer-quality-tools.git
${HOME}/maintainer-quality-tools
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly - travis_install_nightly

24
mail_all/__manifest__.py

@ -3,27 +3,19 @@
"name": "Show all messages", "name": "Show all messages",
"summary": """Checkout all messages where you have access""", "summary": """Checkout all messages where you have access""",
"category": "Discuss", "category": "Discuss",
"images": ['images/1.jpg'],
"version": "1.0.0",
"images": ["images/1.jpg"],
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
'price': 40.00,
'currency': 'EUR',
"depends": [
"mail_base"
],
"price": 40.00,
"currency": "EUR",
"depends": ["mail_base"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
"views/templates.xml",
],
"qweb": [
"static/src/xml/menu.xml",
],
"data": ["views/templates.xml"],
"qweb": ["static/src/xml/menu.xml"],
"demo": [], "demo": [],
'installable': True,
"installable": True,
"auto_install": False, "auto_install": False,
} }

1
mail_all/models/__init__.py

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

93
mail_all/static/src/js/mail_all.js

@ -1,65 +1,60 @@
odoo.define('mail_all.all', function (require) {
"use strict";
odoo.define("mail_all.all", function(require) {
"use strict";
var base_obj = require('mail_base.base');
var base_obj = require("mail_base.base");
//-------------------------------------------------------------------------------
var bus = require('bus.bus').bus;
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
var Model = require('web.Model');
var session = require('web.session');
var time = require('web.time');
var web_client = require('web.web_client');
// -------------------------------------------------------------------------------
var core = require("web.core");
var _lt = core._lt;
//-------------------------------------------------------------------------------
var _lt = core._lt;
// -------------------------------------------------------------------------------
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
ChatAction.include({
get_thread_rendering_options: function (messages) {
var options = this._super.apply(this, arguments);
options.display_subject = options.display_subject || this.channel.id === "channel_all";
return options;
}
});
// Inherit class and override methods
base_obj.MailTools.include({
get_properties: function(msg){
var properties = this._super.apply(this, arguments);
properties.is_all = this.property_descr("channel_all", msg, this);
return properties;
},
set_channel_flags: function(data, msg){
this._super.apply(this, arguments);
msg.is_all = data.author_id != 'ODOOBOT';
return msg;
},
get_channel_array: function(msg){
var arr = this._super.apply(this, arguments);
return arr.concat('channel_all');
},
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({
get_thread_rendering_options: function(messages) {
var options = this._super.apply(this, arguments);
options.display_subject =
options.display_subject || this.channel.id === "channel_all";
return options;
},
});
get_domain: function(channel){
return (channel.id === "channel_all") ? [] : this._super.apply(this, arguments);
}
});
// Inherit class and override methods
base_obj.MailTools.include({
get_properties: function(msg) {
var properties = this._super.apply(this, arguments);
properties.is_all = this.property_descr("channel_all", msg, this);
return properties;
},
set_channel_flags: function(data, msg) {
this._super.apply(this, arguments);
msg.is_all = data.author_id !== "ODOOBOT";
return msg;
},
get_channel_array: function(msg) {
var arr = this._super.apply(this, arguments);
return arr.concat("channel_all");
},
get_domain: function(channel) {
return channel.id === "channel_all"
? []
: this._super.apply(this, arguments);
},
});
base_obj.chat_manager.is_ready.then(function(){
base_obj.chat_manager.is_ready.then(function() {
// Add all channel // Add all channel
base_obj.chat_manager.mail_tools.add_channel({ base_obj.chat_manager.mail_tools.add_channel({
id: "channel_all", id: "channel_all",
name: _lt("All messages"), name: _lt("All messages"),
type: "static"
type: "static",
}); });
return $.when(); return $.when();
}); });
return base_obj.chat_manager;
return base_obj.chat_manager;
}); });

11
mail_all/static/src/xml/menu.xml

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template> <template>
<!--Inherit Sidebar and add All messages menu item after Starred --> <!--Inherit Sidebar and add All messages menu item after Starred -->
<t t-extend="mail.chat.Sidebar"> <t t-extend="mail.chat.Sidebar">
<t t-jquery="div[data-channel-id=channel_starred]" t-operation="after"> <t t-jquery="div[data-channel-id=channel_starred]" t-operation="after">
<div t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_all') ? 'o_active': ''}" data-channel-id="channel_all">
<span class="o_channel_name mail_all"> <i class="fa fa-database"/> All messages </span>
<div
t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_all') ? 'o_active': ''}"
data-channel-id="channel_all"
>
<span class="o_channel_name mail_all"> <i
class="fa fa-database"
/> All messages </span>
</div> </div>
</t> </t>
</t> </t>

7
mail_all/tests/test_js.py

@ -5,7 +5,6 @@ import odoo.tests
@odoo.tests.common.at_install(False) @odoo.tests.common.at_install(False)
@odoo.tests.common.post_install(True) @odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase): class TestUi(odoo.tests.HttpCase):
def test_01_mail_all(self): def test_01_mail_all(self):
# wait till page loaded and then click and wait again # wait till page loaded and then click and wait again
code = """ code = """
@ -14,5 +13,7 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000); setTimeout(function () {console.log('ok');}, 3000);
}, 1000); }, 1000);
""" """
link = '/web#action=%s' % self.ref('mail.mail_channel_action_client_chat')
self.phantom_js(link, code, "odoo.__DEBUG__.services['mail_all.all']", login="admin")
link = "/web#action=%s" % self.ref("mail.mail_channel_action_client_chat")
self.phantom_js(
link, code, "odoo.__DEBUG__.services['mail_all.all']", login="admin"
)

15
mail_all/views/templates.xml

@ -1,11 +1,16 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data> <data>
<template id="mail_all_assets_backend"
name="mail_all_assets_backend"
inherit_id="web.assets_backend">
<template
id="mail_all_assets_backend"
name="mail_all_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script src="/mail_all/static/src/js/mail_all.js" type="text/javascript"></script>
<script
src="/mail_all/static/src/js/mail_all.js"
type="text/javascript"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

1
mail_archives/__init__.py

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

25
mail_archives/__manifest__.py

@ -3,25 +3,16 @@
"name": "Mail archives", "name": "Mail archives",
"summary": """Adds menu to find old messages""", "summary": """Adds menu to find old messages""",
"category": "Discuss", "category": "Discuss",
"images": ['images/1.jpg'],
"version": "1.0.0",
"images": ["images/1.jpg"],
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
'price': 40.00,
'currency': 'EUR',
"depends": [
"mail_base",
],
"data": [
"views/templates.xml",
],
"qweb": [
"static/src/xml/menu.xml",
],
'installable': True,
"price": 40.00,
"currency": "EUR",
"depends": ["mail_base"],
"data": ["views/templates.xml"],
"qweb": ["static/src/xml/menu.xml"],
"installable": True,
} }

126
mail_archives/static/src/js/archives.js

@ -1,85 +1,85 @@
odoo.define('mail_archives.archives', function (require) {
"use strict";
odoo.define("mail_archives.archives", function(require) {
"use strict";
var base_obj = require('mail_base.base');
var base_obj = require("mail_base.base");
//-------------------------------------------------------------------------------
var bus = require('bus.bus').bus;
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
var Model = require('web.Model');
var session = require('web.session');
var time = require('web.time');
var web_client = require('web.web_client');
// -------------------------------------------------------------------------------
var core = require("web.core");
var session = require("web.session");
var _lt = core._lt;
//-------------------------------------------------------------------------------
var _lt = core._lt;
// -------------------------------------------------------------------------------
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
ChatAction.include({
init: function(parent, action, options) {
this._super.apply(this, arguments);
var channel_name = 'channel_archive';
// Add channel Archive for enable "display_subject" option
this.channels_display_subject.push(channel_name);
},
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({
init: function(parent, action, options) {
this._super.apply(this, arguments);
var channel_name = "channel_archive";
// Add channel Archive for enable "display_subject" option
this.channels_display_subject.push(channel_name);
},
update_message_on_current_channel: function(current_channel_id, message){
var result = this._super.apply(this, arguments);
var archive = current_channel_id === "channel_archive" && !message.is_archive;
return archive || result;
}
});
update_message_on_current_channel: function(current_channel_id, message) {
var result = this._super.apply(this, arguments);
var archive =
current_channel_id === "channel_archive" && !message.is_archive;
return archive || result;
},
});
// Inherit class and override methods
base_obj.MailTools.include({
get_properties: function(msg){
var properties = this._super.apply(this, arguments);
properties.is_archive = this.property_descr("channel_archive", msg, this);
return properties;
},
// Inherit class and override methods
base_obj.MailTools.include({
get_properties: function(msg) {
var properties = this._super.apply(this, arguments);
properties.is_archive = this.property_descr("channel_archive", msg, this);
return properties;
},
set_channel_flags: function(data, msg){
this._super.apply(this, arguments);
// Get recipients ids
var recipients_ids = [];
for (var i = 0; i < (data.partner_ids || []).length; i++){
recipients_ids.push(data.partner_ids[i][0]);
}
set_channel_flags: function(data, msg) {
this._super.apply(this, arguments);
// Get recipients ids
var recipients_ids = [];
for (var i = 0; i < (data.partner_ids || []).length; i++) {
recipients_ids.push(data.partner_ids[i][0]);
}
// If author or recipient
if (data.author_id[0] === session.partner_id || recipients_ids.indexOf(session.partner_id) != -1) {
msg.is_archive = true;
}
// If author or recipient
if (
data.author_id[0] === session.partner_id ||
recipients_ids.indexOf(session.partner_id) !== -1
) {
msg.is_archive = true;
}
return msg;
},
return msg;
},
get_channel_array: function(msg){
var arr = this._super.apply(this, arguments);
return arr.concat('channel_archive');
},
get_channel_array: function(msg) {
var arr = this._super.apply(this, arguments);
return arr.concat("channel_archive");
},
get_domain: function(channel){
return (channel.id === "channel_archive") ? [
'|', ['partner_ids', 'in', [openerp.session.partner_id]],
['author_id.user_ids', 'in', [openerp.session.uid]]
] : this._super.apply(this, arguments);
}
});
get_domain: function(channel) {
return channel.id === "channel_archive"
? [
"|",
["partner_ids", "in", [openerp.session.partner_id]],
["author_id.user_ids", "in", [openerp.session.uid]],
]
: this._super.apply(this, arguments);
},
});
base_obj.chat_manager.is_ready.then(function(){
base_obj.chat_manager.is_ready.then(function() {
// Add archive channel // Add archive channel
base_obj.chat_manager.mail_tools.add_channel({ base_obj.chat_manager.mail_tools.add_channel({
id: "channel_archive", id: "channel_archive",
name: _lt("Archive"), name: _lt("Archive"),
type: "static"
type: "static",
}); });
return $.when(); return $.when();
}); });
return base_obj.chat_manager;
return base_obj.chat_manager;
}); });

11
mail_archives/static/src/xml/menu.xml

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template> <template>
<!--Inherit Sidebar and add Archive menu item after Starred --> <!--Inherit Sidebar and add Archive menu item after Starred -->
<t t-extend="mail.chat.Sidebar"> <t t-extend="mail.chat.Sidebar">
<t t-jquery="div[data-channel-id=channel_starred]" t-operation="after"> <t t-jquery="div[data-channel-id=channel_starred]" t-operation="after">
<div t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_archive') ? 'o_active': ''}" data-channel-id="channel_archive">
<span class="o_channel_name mail_archives"> <i class="fa fa-archive"/> Archive </span>
<div
t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_archive') ? 'o_active': ''}"
data-channel-id="channel_archive"
>
<span class="o_channel_name mail_archives"> <i
class="fa fa-archive"
/> Archive </span>
</div> </div>
</t> </t>
</t> </t>

10
mail_archives/tests/test_js.py

@ -5,7 +5,6 @@ import odoo.tests
@odoo.tests.common.at_install(False) @odoo.tests.common.at_install(False)
@odoo.tests.common.post_install(True) @odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase): class TestUi(odoo.tests.HttpCase):
def test_01_mail_archives(self): def test_01_mail_archives(self):
# wait till page loaded and then click and wait again # wait till page loaded and then click and wait again
code = """ code = """
@ -14,5 +13,10 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000); setTimeout(function () {console.log('ok');}, 3000);
}, 1000); }, 1000);
""" """
link = '/web#action=%s' % self.ref('mail.mail_channel_action_client_chat')
self.phantom_js(link, code, "odoo.__DEBUG__.services['mail_archives.archives']", login="admin")
link = "/web#action=%s" % self.ref("mail.mail_channel_action_client_chat")
self.phantom_js(
link,
code,
"odoo.__DEBUG__.services['mail_archives.archives']",
login="admin",
)

15
mail_archives/views/templates.xml

@ -1,11 +1,16 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data> <data>
<template id="res_partner_mails_count_assets_backend"
name="res_partner_mails_count_assets_backend"
inherit_id="web.assets_backend">
<template
id="res_partner_mails_count_assets_backend"
name="res_partner_mails_count_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script src="/mail_archives/static/src/js/archives.js" type="text/javascript"></script>
<script
src="/mail_archives/static/src/js/archives.js"
type="text/javascript"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

21
mail_attachment_popup/__manifest__.py

@ -4,26 +4,17 @@
"summary": """Open attached mail images in popup""", "summary": """Open attached mail images in popup""",
"category": "Extra Tools", "category": "Extra Tools",
"version": "10.0.1.0.0", "version": "10.0.1.0.0",
"images": ['images/popup_image.png'],
"images": ["images/popup_image.png"],
"author": "IT-Projects LLC, Dinar Gabbasov", "author": "IT-Projects LLC, Dinar Gabbasov",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
'website': "https://twitter.com/gabbasov_dinar",
"website": "https://twitter.com/gabbasov_dinar",
"license": "GPL-3", "license": "GPL-3",
"price": "50.0", "price": "50.0",
"currency": "EUR", "currency": "EUR",
"depends": [
"mail",
],
"depends": ["mail"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
"views/mail_attachment_popup_template.xml",
],
"qweb": [
"static/src/xml/mail_attachment_popup.xml",
],
"data": ["views/mail_attachment_popup_template.xml"],
"qweb": ["static/src/xml/mail_attachment_popup.xml"],
"installable": True, "installable": True,
'auto_install': False,
"auto_install": False,
} }

49
mail_attachment_popup/static/src/css/jquery.arcticmodal.css

@ -1,8 +1,43 @@
.arcticmodal-overlay, .arcticmodal-overlay,
.arcticmodal-container { position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 1010; }
.arcticmodal-container { overflow: auto; margin: 0; padding: 0; border: 0; border-collapse: collapse; }
*:first-child+html .arcticmodal-container { height: 100% }
.arcticmodal-container_i { height: 100%; margin: 0 auto; }
.arcticmodal-container_i2 { padding: 24px; margin: 0; border: 0; vertical-align: middle; padding-top: 50px;}
.arcticmodal-error { padding: 20px; border-radius: 10px; background: #000; color: #fff; }
.arcticmodal-loading { width: 80px; height: 80px; border-radius: 10px; background: #000 url(/mail_attachment_popup/static/src/img/loading.gif) no-repeat 50% 50%; }
.arcticmodal-container {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 1010;
}
.arcticmodal-container {
overflow: auto;
margin: 0;
padding: 0;
border: 0;
border-collapse: collapse;
}
*:first-child + html .arcticmodal-container {
height: 100%;
}
.arcticmodal-container_i {
height: 100%;
margin: 0 auto;
}
.arcticmodal-container_i2 {
padding: 24px;
margin: 0;
border: 0;
vertical-align: middle;
padding-top: 50px;
}
.arcticmodal-error {
padding: 20px;
border-radius: 10px;
background: #000;
color: #fff;
}
.arcticmodal-loading {
width: 80px;
height: 80px;
border-radius: 10px;
background: #000 url(/mail_attachment_popup/static/src/img/loading.gif) no-repeat
50% 50%;
}

28
mail_attachment_popup/static/src/css/simple.css

@ -1,11 +1,21 @@
.box-modal { .box-modal {
position: relative;
padding: 16px;
background: #fff;
color: #3c3c3c;
font: 14px/18px Arial, "Helvetica CY", "Nimbus Sans L", sans-serif;
box-shadow: 0 0 0 6px rgba(153, 153, 153, .3);
border-radius: 6px;
position: relative;
padding: 16px;
background: #fff;
color: #3c3c3c;
font: 14px/18px Arial, "Helvetica CY", "Nimbus Sans L", sans-serif;
box-shadow: 0 0 0 6px rgba(153, 153, 153, 0.3);
border-radius: 6px;
}
.box-modal_close {
position: absolute;
right: -25px;
top: -25px;
font-size: 30px;
line-height: 15px;
color: #ffffff;
cursor: pointer;
}
.box-modal_close:hover {
color: #b1b1b1;
} }
.box-modal_close { position: absolute; right: -25px; top: -25px; font-size: 30px; line-height: 15px; color: #ffffff; cursor: pointer; }
.box-modal_close:hover { color: #B1B1B1; }

50
mail_attachment_popup/static/src/xml/mail_attachment_popup.xml

@ -1,27 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template> <template>
<t t-extend="mail.Attachment"> <t t-extend="mail.Attachment">
<t t-jquery="div[t-att-title='attachment.name'] .o_image" t-operation="replace"> <t t-jquery="div[t-att-title='attachment.name'] .o_image" t-operation="replace">
<t t-if="attachment.mimetype and attachment.mimetype.search('image/') !== -1">
<span class="m-dotted" t-attf-onclick="$('#ImageModal{{ attachment.id }}').arcticmodal()">
<div class="o_image" target="_blank" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80">
<span class='o_attachment_name'><t t-esc='attachment.name'/></span>
<t
t-if="attachment.mimetype and attachment.mimetype.search('image/') !== -1"
>
<span
class="m-dotted"
t-attf-onclick="$('#ImageModal{{ attachment.id }}').arcticmodal()"
>
<div
class="o_image"
target="_blank"
t-att-data-mimetype="attachment.mimetype"
t-attf-data-src="/web/image/#{attachment.id}/100x80"
>
<span class='o_attachment_name'><t
t-esc='attachment.name'
/></span>
</div> </div>
</span> </span>
<div class="g-hidden"> <div class="g-hidden">
<div class="box-modal" t-attf-id="ImageModal{{ attachment.id }}"> <div class="box-modal" t-attf-id="ImageModal{{ attachment.id }}">
<div class="box-modal_close arcticmodal-close">X</div> <div class="box-modal_close arcticmodal-close">X</div>
<img t-att-data-mimetype="attachment.mimetype" t-attf-src="/web/image/#{attachment.id}"></img>
<img
t-att-data-mimetype="attachment.mimetype"
t-attf-src="/web/image/#{attachment.id}"
/>
<ul class="box-modal-li"> <ul class="box-modal-li">
<li><span class='o_attachment_name'><t t-esc='attachment.name'/></span></li>
<li><span class='oe_download_original_img'><a t-att-href='attachment.url' target="_blank">Download</a></span></li>
<li><span class='o_attachment_name'><t
t-esc='attachment.name'
/></span></li>
<li><span class='oe_download_original_img'><a
t-att-href='attachment.url'
target="_blank"
>Download</a></span></li>
</ul> </ul>
</div> </div>
</div> </div>
</t> </t>
<t t-if="! (attachment.mimetype and attachment.mimetype.search('image/') !== -1)">
<a class="o_image" t-att-href='attachment.url' target="_blank" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80">
<span class='o_attachment_name'><t t-esc='attachment.name'/></span>
<t
t-if="! (attachment.mimetype and attachment.mimetype.search('image/') !== -1)"
>
<a
class="o_image"
t-att-href='attachment.url'
target="_blank"
t-att-data-mimetype="attachment.mimetype"
t-attf-data-src="/web/image/#{attachment.id}/100x80"
>
<span class='o_attachment_name'><t t-esc='attachment.name' /></span>
</a> </a>
</t> </t>
</t> </t>

28
mail_attachment_popup/views/mail_attachment_popup_template.xml

@ -1,12 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp> <openerp>
<data> <data>
<template id="assets_backend" name="mail attachment popup assets" inherit_id="web.assets_backend">
<template
id="assets_backend"
name="mail attachment popup assets"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" href="/mail_attachment_popup/static/src/css/jquery.arcticmodal.css"/>
<link rel="stylesheet" href="/mail_attachment_popup/static/src/css/simple.css"/>
<link rel="stylesheet" href="/mail_attachment_popup/static/src/css/styles.css"/>
<script type="text/javascript" src="/mail_attachment_popup/static/lib/js/jquery.arcticmodal.js"></script>
<link
rel="stylesheet"
href="/mail_attachment_popup/static/src/css/jquery.arcticmodal.css"
/>
<link
rel="stylesheet"
href="/mail_attachment_popup/static/src/css/simple.css"
/>
<link
rel="stylesheet"
href="/mail_attachment_popup/static/src/css/styles.css"
/>
<script
type="text/javascript"
src="/mail_attachment_popup/static/lib/js/jquery.arcticmodal.js"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

18
mail_base/__manifest__.py

@ -9,21 +9,13 @@
"category": "Discuss", "category": "Discuss",
"images": [], "images": [],
"version": "10.0.1.1.0", "version": "10.0.1.1.0",
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
'price': 9.00,
'currency': 'EUR',
"depends": [
"base",
"mail"
],
"data": [
"views/templates.xml",
],
'installable': True,
"price": 9.00,
"currency": "EUR",
"depends": ["base", "mail"],
"data": ["views/templates.xml"],
"installable": True,
} }

1
mail_base/controllers/__init__.py

@ -1 +1,2 @@
# -*- coding: utf-8 -*-
from . import main from . import main

6
mail_base/controllers/main.py

@ -2,8 +2,8 @@
# Copyright 2017 mikaelh <https://github.com/mikaelh> # Copyright 2017 mikaelh <https://github.com/mikaelh>
# Copyright 2017-2019 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2017-2019 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html) # License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html)
from openerp.http import request
from openerp.addons.bus.controllers.main import BusController from openerp.addons.bus.controllers.main import BusController
from openerp.http import request
class MailChatController(BusController): class MailChatController(BusController):
@ -13,7 +13,7 @@ class MailChatController(BusController):
def _poll(self, dbname, channels, last, options): def _poll(self, dbname, channels, last, options):
if request.session.uid: if request.session.uid:
channels = list(channels) # do not alter original list
channels.append((request.db, 'mail_base.mail_sent'))
channels = list(channels) # do not alter original list
channels.append((request.db, "mail_base.mail_sent"))
return super(MailChatController, self)._poll(dbname, channels, last, options) return super(MailChatController, self)._poll(dbname, channels, last, options)

18
mail_base/models.py

@ -7,28 +7,30 @@ from openerp import api, models
class MailMessage(models.Model): class MailMessage(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
@api.multi @api.multi
def write(self, values): def write(self, values):
if values.get('needaction_partner_ids'):
if not values.get('partner_ids'):
values['partner_ids'] = []
for triplet in values.get('needaction_partner_ids'):
if values.get("needaction_partner_ids"):
if not values.get("partner_ids"):
values["partner_ids"] = []
for triplet in values.get("needaction_partner_ids"):
if triplet[0] == 6: if triplet[0] == 6:
for i in triplet[2]: for i in triplet[2]:
values['partner_ids'].append((4, i, False))
values["partner_ids"].append((4, i, False))
return super(MailMessage, self).write(values) return super(MailMessage, self).write(values)
class MailComposer(models.TransientModel): class MailComposer(models.TransientModel):
_inherit = 'mail.compose.message'
_inherit = "mail.compose.message"
@api.multi @api.multi
def send_mail(self, auto_commit=False): def send_mail(self, auto_commit=False):
res = super(MailComposer, self).send_mail(auto_commit=auto_commit) res = super(MailComposer, self).send_mail(auto_commit=auto_commit)
notification = {} notification = {}
self.env['bus.bus'].sendone((self._cr.dbname, 'mail_base.mail_sent'), notification)
self.env["bus.bus"].sendone(
(self._cr.dbname, "mail_base.mail_sent"), notification
)
return res return res

12
mail_base/views/templates.xml

@ -1,11 +1,13 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data> <data>
<template id="mail_base_assets_backend"
name="mail_base_assets_backend"
inherit_id="web.assets_backend">
<template
id="mail_base_assets_backend"
name="mail_base_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script src="/mail_base/static/lib/base.js" type="text/javascript"></script>
<script src="/mail_base/static/lib/base.js" type="text/javascript" />
</xpath> </xpath>
</template> </template>
</data> </data>

26
mail_check_immediately/__manifest__.py

@ -1,20 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': 'Check mail immediately',
'version': '1.0.1',
'author': 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
"name": "Check mail immediately",
"vesion": "10.0.1.0.1",
"author": "IT-Projects LLC, Ivan Yelizariev",
"license": "LGPL-3",
"category": "Discuss", "category": "Discuss",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
'website': 'https://twitter.com/yelizariev',
'price': 9.00,
'currency': 'EUR',
'depends': ['base', 'web', 'fetchmail', 'mail'],
'data': [
'views.xml',
],
'qweb': [
"static/src/xml/main.xml",
],
'installable': False
"website": "https://twitter.com/yelizariev",
"price": 9.00,
"currency": "EUR",
"depends": ["base", "web", "fetchmail", "mail"],
"data": ["views.xml"],
"qweb": ["static/src/xml/main.xml"],
"installable": False,
} }

49
mail_check_immediately/models.py

@ -1,16 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime import datetime
from openerp import api, exceptions, fields, models, tools
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp import tools
from openerp import exceptions
from openerp import models, fields, api
class FetchMailServer(models.Model): class FetchMailServer(models.Model):
_inherit = 'fetchmail.server'
_name = 'fetchmail.server'
_inherit = "fetchmail.server"
_name = "fetchmail.server"
_last_updated = None _last_updated = None
@ -20,36 +17,54 @@ class FetchMailServer(models.Model):
if not self._last_updated: if not self._last_updated:
self._last_updated = tools.datetime.now() self._last_updated = tools.datetime.now()
src_tstamp_str = self._last_updated.strftime(tools.misc.DEFAULT_SERVER_DATETIME_FORMAT)
src_tstamp_str = self._last_updated.strftime(
tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
)
src_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT src_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
dst_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT dst_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
dst_tz_name = self._context.get('tz') or self.env.user.tz
_now = tools.misc.server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name)
dst_tz_name = self._context.get("tz") or self.env.user.tz
_now = tools.misc.server_to_local_timestamp(
src_tstamp_str, src_format, dst_format, dst_tz_name
)
return _now return _now
@api.model @api.model
def _fetch_mails(self): def _fetch_mails(self):
if self._context.get('run_fetchmail_manually'):
if self._context.get("run_fetchmail_manually"):
# if interval less than 5 seconds # if interval less than 5 seconds
if self._last_updated and (datetime.datetime.now() - self._last_updated) < datetime.timedelta(0, 5):
raise exceptions.Warning(_('Error'), _('Task can be started no earlier than 5 seconds.'))
if self._last_updated and (
datetime.datetime.now() - self._last_updated
) < datetime.timedelta(0, 5):
raise exceptions.Warning(
_("Error"), _("Task can be started no earlier than 5 seconds.")
)
super(FetchMailServer, self)._fetch_mails() super(FetchMailServer, self)._fetch_mails()
res = self.env['fetchmail.server'].sudo().with_context(tz=self.env.user.tz).search([('state', '=', 'done')])
res = (
self.env["fetchmail.server"]
.sudo()
.with_context(tz=self.env.user.tz)
.search([("state", "=", "done")])
)
if res: if res:
res[0].run_time = self._run_time() res[0].run_time = self._run_time()
class FetchMailImmediately(models.AbstractModel): class FetchMailImmediately(models.AbstractModel):
_name = 'fetch_mail.imm'
_name = "fetch_mail.imm"
@api.model @api.model
def get_last_update_time(self): def get_last_update_time(self):
res = self.env['fetchmail.server'].sudo().with_context(tz=self.env.user.tz).search([('state', '=', 'done')])
res = (
self.env["fetchmail.server"]
.sudo()
.with_context(tz=self.env.user.tz)
.search([("state", "=", "done")])
)
array = [r.run_time for r in res] array = [r.run_time for r in res]
if array: if array:
return array[0] return array[0]
@ -59,8 +74,8 @@ class FetchMailImmediately(models.AbstractModel):
@api.model @api.model
def run_fetchmail_manually(self): def run_fetchmail_manually(self):
fetchmail_task = self.env.ref('fetchmail.ir_cron_mail_gateway_action')
fetchmail_model = self.env['fetchmail.server'].sudo()
fetchmail_task = self.env.ref("fetchmail.ir_cron_mail_gateway_action")
fetchmail_model = self.env["fetchmail.server"].sudo()
fetchmail_task._try_lock() fetchmail_task._try_lock()
fetchmail_model.with_context(run_fetchmail_manually=True)._fetch_mails() fetchmail_model.with_context(run_fetchmail_manually=True)._fetch_mails()

53
mail_check_immediately/static/src/js/main.js

@ -1,14 +1,13 @@
openerp.mail_check_immediately = function(instance, local) { openerp.mail_check_immediately = function(instance, local) {
"use strict";
instance.mail.Wall.include({ instance.mail.Wall.include({
init: function(){
init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
var _this = this; var _this = this;
this.imm_model = new instance.web.Model('fetch_mail.imm');
this.events['click a.oe_fetch_new_mails'] = function(){
this.imm_model = new instance.web.Model("fetch_mail.imm");
this.events["click a.oe_fetch_new_mails"] = function() {
_this.run_fetchmail_manually(); _this.run_fetchmail_manually();
}; };
}, },
@ -16,40 +15,46 @@ openerp.mail_check_immediately = function(instance, local) {
start: function() { start: function() {
var _this = this; var _this = this;
this._super(); this._super();
this.get_last_fetched_time(); this.get_last_fetched_time();
this.get_time_loop = setInterval(function(){
this.get_time_loop = setInterval(function() {
_this.get_last_fetched_time(); _this.get_last_fetched_time();
}, 30000); }, 30000);
}, },
run_fetchmail_manually: function(){
run_fetchmail_manually: function() {
var _this = this; var _this = this;
this.imm_model.call('run_fetchmail_manually', {context: new instance.web.CompoundContext()}).then(function(){
_this.get_last_fetched_time();
});
this.imm_model
.call("run_fetchmail_manually", {
context: new instance.web.CompoundContext(),
})
.then(function() {
_this.get_last_fetched_time();
});
}, },
get_last_fetched_time: function(){
get_last_fetched_time: function() {
var _this = this; var _this = this;
this.imm_model.call('get_last_update_time', {context: new instance.web.CompoundContext()}).then(function(res){
var value;
if (res)
value = $.timeago(res);
value = value || 'undefined';
_this.$el.find('span.oe_view_manager_fetch_mail_imm_field').html(value);
});
this.imm_model
.call("get_last_update_time", {
context: new instance.web.CompoundContext(),
})
.then(function(res) {
var value = null;
if (res) value = $.timeago(res);
value = value || "undefined";
_this.$el
.find("span.oe_view_manager_fetch_mail_imm_field")
.html(value);
});
}, },
destroy: function(){
destroy: function() {
clearInterval(this.get_time_loop); clearInterval(this.get_time_loop);
this._super.apply(this, arguments);
}
this._super.apply(this, arguments);
},
}); });
}; };

14
mail_check_immediately/static/src/xml/main.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<templates> <templates>
<t t-name="fetch_mail_immediately.header"> <t t-name="fetch_mail_immediately.header">
<tr class="oe_header_row"> <tr class="oe_header_row">
@ -6,19 +6,23 @@
<div class="oe_view_manager_fetch_mail_imm"> <div class="oe_view_manager_fetch_mail_imm">
<em> <em>
<span>Mails fetched:</span> <span>Mails fetched:</span>
<a href="#" class="oe_fetch_new_mails" title="Click to fetch mails now">
<span class="oe_view_manager_fetch_mail_imm_field"></span>
<a
href="#"
class="oe_fetch_new_mails"
title="Click to fetch mails now"
>
<span class="oe_view_manager_fetch_mail_imm_field" />
</a> </a>
</em> </em>
</div> </div>
</td> </td>
<td></td>
<td />
</tr> </tr>
</t> </t>
<t t-extend="mail.wall"> <t t-extend="mail.wall">
<t t-jquery="tr.oe_header_row_top" t-operation="after"> <t t-jquery="tr.oe_header_row_top" t-operation="after">
<t t-call="fetch_mail_immediately.header"> <t t-call="fetch_mail_immediately.header">
<t t-set="colspan" t-value="2"/>
<t t-set="colspan" t-value="2" />
</t> </t>
</t> </t>
</t> </t>

11
mail_check_immediately/views.xml

@ -1,8 +1,15 @@
<openerp> <openerp>
<data> <data>
<template id="assets_backend_inherited_check_mail" name="Check mail immediately bar" inherit_id="web.assets_backend">
<template
id="assets_backend_inherited_check_mail"
name="Check mail immediately bar"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script type="text/javascript" src="/mail_check_immediately/static/src/js/main.js"></script>
<script
type="text/javascript"
src="/mail_check_immediately/static/src/js/main.js"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

6
mail_fix_553/__manifest__.py

@ -1,13 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
"name": "Fix mail error 553", "name": "Fix mail error 553",
"version": "0.3",
"version": "10.0.1.0.3",
"author": "IT-Projects LLC, Ivan Yelizariev", "author": "IT-Projects LLC, Ivan Yelizariev",
'license': 'LGPL-3',
"license": "LGPL-3",
"category": "Discuss", "category": "Discuss",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://yelizariev.github.io", "website": "https://yelizariev.github.io",
"depends": ["base", "mail"], "depends": ["base", "mail"],
"data": ["data.xml"], "data": ["data.xml"],
'installable': False
"installable": False,
} }

2
mail_fix_553/data.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp> <openerp>
<data noupdate="1"> <data noupdate="1">
<!-- Catchall Email Alias --> <!-- Catchall Email Alias -->

157
mail_fix_553/mail_fix_553.py

@ -1,15 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# TODO
# pylint: disable=old-api7-method-defined,invalid-commit
import base64 import base64
import logging import logging
import re import re
from email.utils import formataddr from email.utils import formataddr
from openerp import tools
from openerp import SUPERUSER_ID
from openerp import SUPERUSER_ID, tools
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import osv from openerp.osv import osv
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _ from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -18,7 +18,9 @@ _logger = logging.getLogger(__name__)
class MailMail(osv.Model): class MailMail(osv.Model):
_inherit = "mail.mail" _inherit = "mail.mail"
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
def send(
self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None
):
# copy-paste from addons/mail/mail_mail.py # copy-paste from addons/mail/mail_mail.py
""" Sends the selected emails immediately, ignoring their current """ Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed state (mails that have already been sent should not be passed
@ -36,60 +38,93 @@ class MailMail(osv.Model):
""" """
# NEW STUFF # NEW STUFF
catchall_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.alias_from", context=context)
catchall_alias_name = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.name_alias_from", context=context)
catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
correct_email_from = r'@%s>?\s*$' % catchall_domain
default_email_from = '%s@%s' % (catchall_alias, catchall_domain)
catchall_alias = self.pool["ir.config_parameter"].get_param(
cr, uid, "mail.catchall.alias_from", context=context
)
catchall_alias_name = self.pool["ir.config_parameter"].get_param(
cr, uid, "mail.catchall.name_alias_from", context=context
)
catchall_domain = self.pool["ir.config_parameter"].get_param(
cr, uid, "mail.catchall.domain", context=context
)
correct_email_from = r"@%s>?\s*$" % catchall_domain
default_email_from = "{}@{}".format(catchall_alias, catchall_domain)
context = dict(context or {}) context = dict(context or {})
ir_mail_server = self.pool.get('ir.mail_server')
ir_attachment = self.pool['ir.attachment']
ir_mail_server = self.pool.get("ir.mail_server")
ir_attachment = self.pool["ir.attachment"]
for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): for mail in self.browse(cr, SUPERUSER_ID, ids, context=context):
try: try:
# TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method
if mail.model: if mail.model:
model_id = self.pool['ir.model'].search(cr, SUPERUSER_ID, [('model', '=', mail.model)], context=context)[0]
model = self.pool['ir.model'].browse(cr, SUPERUSER_ID, model_id, context=context)
model_id = self.pool["ir.model"].search(
cr, SUPERUSER_ID, [("model", "=", mail.model)], context=context
)[0]
model = self.pool["ir.model"].browse(
cr, SUPERUSER_ID, model_id, context=context
)
else: else:
model = None model = None
if model: if model:
context['model_name'] = model.name
context["model_name"] = model.name
# load attachment binary data with a separate read(), as prefetching all # load attachment binary data with a separate read(), as prefetching all
# `datas` (binary field) could bloat the browse cache, triggerring # `datas` (binary field) could bloat the browse cache, triggerring
# soft/hard mem limits with temporary data. # soft/hard mem limits with temporary data.
attachment_ids = [a.id for a in mail.attachment_ids] attachment_ids = [a.id for a in mail.attachment_ids]
attachments = [(a['datas_fname'], base64.b64decode(a['datas']))
for a in ir_attachment.read(cr, SUPERUSER_ID, attachment_ids,
['datas_fname', 'datas'])]
attachments = [
(a["datas_fname"], base64.b64decode(a["datas"]))
for a in ir_attachment.read(
cr, SUPERUSER_ID, attachment_ids, ["datas_fname", "datas"]
)
]
# specific behavior to customize the send email for notified partners # specific behavior to customize the send email for notified partners
email_list = [] email_list = []
if mail.email_to: if mail.email_to:
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
email_list.append(
self.send_get_email_dict(cr, uid, mail, context=context)
)
for partner in mail.recipient_ids: for partner in mail.recipient_ids:
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
email_list.append(
self.send_get_email_dict(
cr, uid, mail, partner=partner, context=context
)
)
# headers # headers
headers = {} headers = {}
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
bounce_alias = self.pool["ir.config_parameter"].get_param(
cr, uid, "mail.bounce.alias", context=context
)
catchall_domain = self.pool["ir.config_parameter"].get_param(
cr, uid, "mail.catchall.domain", context=context
)
if bounce_alias and catchall_domain: if bounce_alias and catchall_domain:
if mail.model and mail.res_id: if mail.model and mail.res_id:
headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain)
headers["Return-Path"] = "%s-%d-%s-%d@%s" % (
bounce_alias,
mail.id,
mail.model,
mail.res_id,
catchall_domain,
)
else: else:
headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain)
headers["Return-Path"] = "%s-%d@%s" % (
bounce_alias,
mail.id,
catchall_domain,
)
if mail.headers: if mail.headers:
try: try:
headers.update(eval(mail.headers))
headers.update(safe_eval(mail.headers))
except Exception: except Exception:
pass pass
# Writing on the mail object may fail (e.g. lock on user) which # Writing on the mail object may fail (e.g. lock on user) which
# would trigger a rollback *after* actually sending the email. # would trigger a rollback *after* actually sending the email.
# To avoid sending twice the same email, provoke the failure earlier # To avoid sending twice the same email, provoke the failure earlier
mail.write({'state': 'exception'})
mail.write({"state": "exception"})
mail_sent = False mail_sent = False
# build an RFC2822 email.message.Message object and send it without queuing # build an RFC2822 email.message.Message object and send it without queuing
@ -105,58 +140,78 @@ class MailMail(osv.Model):
msg = ir_mail_server.build_email( msg = ir_mail_server.build_email(
email_from=email_from, # NEW STUFF email_from=email_from, # NEW STUFF
email_to=email.get('email_to'),
subject=email.get('subject'),
body=email.get('body'),
body_alternative=email.get('body_alternative'),
email_to=email.get("email_to"),
subject=email.get("subject"),
body=email.get("body"),
body_alternative=email.get("body_alternative"),
email_cc=tools.email_split(mail.email_cc), email_cc=tools.email_split(mail.email_cc),
reply_to=mail.reply_to, reply_to=mail.reply_to,
attachments=attachments, attachments=attachments,
message_id=mail.message_id, message_id=mail.message_id,
references=mail.references, references=mail.references,
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
subtype='html',
subtype_alternative='plain',
headers=headers)
object_id=mail.res_id
and ("{}-{}".format(mail.res_id, mail.model)),
subtype="html",
subtype_alternative="plain",
headers=headers,
)
try: try:
res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=mail.mail_server_id.id,
context=context)
res = ir_mail_server.send_email(
cr,
uid,
msg,
mail_server_id=mail.mail_server_id.id,
context=context,
)
except AssertionError as error: except AssertionError as error:
if error.message == ir_mail_server.NO_VALID_RECIPIENT:
if str(error) == ir_mail_server.NO_VALID_RECIPIENT:
# No valid recipient found for this particular # No valid recipient found for this particular
# mail item -> ignore error to avoid blocking # mail item -> ignore error to avoid blocking
# delivery to next recipients, if any. If this is # delivery to next recipients, if any. If this is
# the only recipient, the mail will show as failed. # the only recipient, the mail will show as failed.
_logger.warning("Ignoring invalid recipients for mail.mail %s: %s",
mail.message_id, email.get('email_to'))
_logger.warning(
"Ignoring invalid recipients for mail.mail %s: %s",
mail.message_id,
email.get("email_to"),
)
else: else:
raise raise
if res: if res:
mail.write({'state': 'sent', 'message_id': res})
mail.write({"state": "sent", "message_id": res})
mail_sent = True mail_sent = True
# /!\ can't use mail.state here, as mail.refresh() will cause an error # /!\ can't use mail.state here, as mail.refresh() will cause an error
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1 # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
if mail_sent: if mail_sent:
_logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id)
self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent)
_logger.info(
"Mail with ID %r and Message-Id %r successfully sent",
mail.id,
mail.message_id,
)
self._postprocess_sent_message(
cr, uid, mail, context=context, mail_sent=mail_sent
)
except MemoryError: except MemoryError:
# prevent catching transient MemoryErrors, bubble up to notify user or abort cron job # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
# instead of marking the mail as failed # instead of marking the mail as failed
_logger.exception('MemoryError while processing mail with ID %r and Msg-Id %r. '
'Consider raising the --limit-memory-hard startup option',
mail.id, mail.message_id)
_logger.exception(
"MemoryError while processing mail with ID %r and Msg-Id %r. "
"Consider raising the --limit-memory-hard startup option",
mail.id,
mail.message_id,
)
raise raise
except Exception as e: except Exception as e:
_logger.exception('failed sending mail.mail %s', mail.id)
mail.write({'state': 'exception'})
self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=False)
_logger.exception("failed sending mail.mail %s", mail.id)
mail.write({"state": "exception"})
self._postprocess_sent_message(
cr, uid, mail, context=context, mail_sent=False
)
if raise_exception: if raise_exception:
if isinstance(e, AssertionError): if isinstance(e, AssertionError):
# get the args of the original error, wrap into a value and throw a MailDeliveryException # get the args of the original error, wrap into a value and throw a MailDeliveryException
# that is an except_orm, with name and value as arguments # that is an except_orm, with name and value as arguments
value = '. '.join(e.args)
value = ". ".join(e.args)
raise MailDeliveryException(_("Mail Delivery Failed"), value) raise MailDeliveryException(_("Mail Delivery Failed"), value)
raise raise

31
mail_move_message/__manifest__.py

@ -1,22 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': 'Mail relocation',
'version': '10.0.1.0.6',
'author': 'IT-Projects LLC, Ivan Yelizariev, Pavel Romanchenko',
'license': 'LGPL-3',
'category': 'Discuss',
'images': ['images/m1.png'],
"name": "Mail relocation",
"version": "10.0.1.0.6",
"author": "IT-Projects LLC, Ivan Yelizariev, Pavel Romanchenko",
"license": "LGPL-3",
"category": "Discuss",
"images": ["images/m1.png"],
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
'website': 'https://twitter.com/yelizariev',
'price': 100.00,
'currency': 'EUR',
'depends': ['mail_all', 'web_polymorphic_field'],
'data': [
'mail_move_message_views.xml',
'data/mail_move_message_data.xml',
],
'qweb': [
'static/src/xml/mail_move_message_main.xml',
],
'installable': True,
"website": "https://twitter.com/yelizariev",
"price": 100.00,
"currency": "EUR",
"depends": ["mail_all", "web_polymorphic_field"],
"data": ["mail_move_message_views.xml", "data/mail_move_message_data.xml"],
"qweb": ["static/src/xml/mail_move_message_main.xml"],
"installable": True,
} }

46
mail_move_message/controllers/main.py

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from odoo.addons.web.controllers.main import DataSet
from odoo.tools.translate import _
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.tools.translate import _
from odoo.addons.bus.controllers.main import BusController from odoo.addons.bus.controllers.main import BusController
from odoo.addons.web.controllers.main import DataSet
class MailChatController(BusController): class MailChatController(BusController):
@ -13,56 +14,57 @@ class MailChatController(BusController):
def _poll(self, dbname, channels, last, options): def _poll(self, dbname, channels, last, options):
if request.session.uid: if request.session.uid:
channels = list(channels) # do not alter original list
channels.append((request.db, 'mail_move_message'))
channels.append((request.db, 'mail_move_message.delete_message'))
channels = list(channels) # do not alter original list
channels.append((request.db, "mail_move_message"))
channels.append((request.db, "mail_move_message.delete_message"))
return super(MailChatController, self)._poll(dbname, channels, last, options) return super(MailChatController, self)._poll(dbname, channels, last, options)
class DataSetCustom(DataSet): class DataSetCustom(DataSet):
def _extend_name(self, model, records): def _extend_name(self, model, records):
Model = request.env[model] Model = request.env[model]
fields = Model.fields_get() fields = Model.fields_get()
contact_field = False contact_field = False
for n, f in fields.iteritems(): for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
if f["type"] == "many2one" and f["relation"] == "res.partner":
contact_field = n contact_field = n
break break
partner_info = {} partner_info = {}
if contact_field: if contact_field:
partner_info = Model.browse([r[0] for r in records]).read([contact_field]) partner_info = Model.browse([r[0] for r in records]).read([contact_field])
partner_info = dict([(p['id'], p[contact_field]) for p in partner_info])
partner_info = {p["id"]: p[contact_field] for p in partner_info}
res = [] res = []
for r in records: for r in records:
if partner_info.get(r[0]): if partner_info.get(r[0]):
res.append((r[0], _('%s [%s] ID %s') % (r[1], partner_info.get(r[0])[1], r[0])))
res.append(
(r[0], _("%s [%s] ID %s") % (r[1], partner_info.get(r[0])[1], r[0]))
)
else: else:
res.append((r[0], _('%s ID %s') % (r[1], r[0])))
res.append((r[0], _("%s ID %s") % (r[1], r[0])))
return res return res
@http.route('/web/dataset/call_kw/<model>/name_search', type='json', auth="user")
@http.route("/web/dataset/call_kw/<model>/name_search", type="json", auth="user")
def name_search(self, model, method, args, kwargs): def name_search(self, model, method, args, kwargs):
context = kwargs.get('context')
if context and context.get('extended_name_with_contact'):
context = kwargs.get("context")
if context and context.get("extended_name_with_contact"):
# add order by ID desc # add order by ID desc
Model = request.env[model] Model = request.env[model]
search_args = list(kwargs.get('args') or [])
limit = int(kwargs.get('limit') or 100)
operator = kwargs.get('operator')
name = kwargs.get('name')
if Model._rec_name and (not name == '' and operator == 'ilike'):
search_args = list(kwargs.get("args") or [])
limit = int(kwargs.get("limit") or 100)
operator = kwargs.get("operator")
name = kwargs.get("name")
if Model._rec_name and (not name == "" and operator == "ilike"):
search_args += [(Model._rec_name, operator, name)] search_args += [(Model._rec_name, operator, name)]
records = Model.search(search_args, limit=limit, order='id desc')
records = Model.search(search_args, limit=limit, order="id desc")
res = records.name_get() res = records.name_get()
return self._extend_name(model, res) return self._extend_name(model, res)
return self._call_kw(model, method, args, kwargs) return self._call_kw(model, method, args, kwargs)
@http.route('/web/dataset/call_kw/<model>/name_get', type='json', auth="user")
@http.route("/web/dataset/call_kw/<model>/name_get", type="json", auth="user")
def name_get(self, model, method, args, kwargs): def name_get(self, model, method, args, kwargs):
res = self._call_kw(model, method, args, kwargs) res = self._call_kw(model, method, args, kwargs)
context = kwargs.get('context')
if context and context.get('extended_name_with_contact'):
context = kwargs.get("context")
if context and context.get("extended_name_with_contact"):
res = self._extend_name(model, res) res = self._extend_name(model, res)
return res return res

2
mail_move_message/data/mail_move_message_data.xml

@ -1,4 +1,4 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data noupdate="1"> <data noupdate="1">
<record id="mail_relocation_models" model="ir.config_parameter"> <record id="mail_relocation_models" model="ir.config_parameter">

1
mail_move_message/doc/index.rst

@ -1,4 +1,3 @@
================= =================
Mail relocation Mail relocation
================= =================

451
mail_move_message/mail_move_message_models.py

@ -1,28 +1,31 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from openerp import api
from openerp import fields
from openerp import models
from openerp import api, fields, models
from openerp.tools import email_split from openerp.tools import email_split
from openerp.tools.translate import _ from openerp.tools.translate import _
class Wizard(models.TransientModel): class Wizard(models.TransientModel):
_name = 'mail_move_message.wizard'
_name = "mail_move_message.wizard"
def _model_selection(self): def _model_selection(self):
selection = [] selection = []
config_parameters = self.env['ir.config_parameter']
model_names = config_parameters.get_param('mail_relocation_models')
model_names = model_names.split(',') if model_names else []
if 'default_message_id' in self.env.context:
message = self.env['mail.message'].browse(self.env.context['default_message_id'])
config_parameters = self.env["ir.config_parameter"]
model_names = config_parameters.get_param("mail_relocation_models")
model_names = model_names.split(",") if model_names else []
if "default_message_id" in self.env.context:
message = self.env["mail.message"].browse(
self.env.context["default_message_id"]
)
if message.model and message.model not in model_names: if message.model and message.model not in model_names:
model_names.append(message.model) model_names.append(message.model)
if message.moved_from_model and message.moved_from_model not in model_names: if message.moved_from_model and message.moved_from_model not in model_names:
model_names.append(message.moved_from_model) model_names.append(message.moved_from_model)
if model_names: if model_names:
selection = [(m.model, m.display_name) for m in self.env['ir.model'].search([('model', 'in', model_names)])]
selection = [
(m.model, m.display_name)
for m in self.env["ir.model"].search([("model", "in", model_names)])
]
return selection return selection
@ -31,69 +34,107 @@ class Wizard(models.TransientModel):
res = super(Wizard, self).default_get(fields_list) res = super(Wizard, self).default_get(fields_list)
model_fields = self.fields_get() model_fields = self.fields_get()
if model_fields['model']['selection']:
res['model'] = model_fields['model']['selection'] and model_fields['model']['selection'][0][0]
if 'message_id' in res:
message = self.env['mail.message'].browse(res['message_id'])
if model_fields["model"]["selection"]:
res["model"] = (
model_fields["model"]["selection"]
and model_fields["model"]["selection"][0][0]
)
if "message_id" in res:
message = self.env["mail.message"].browse(res["message_id"])
email_from = message.email_from email_from = message.email_from
parts = email_split(email_from.replace(' ', ','))
parts = email_split(email_from.replace(" ", ","))
if parts: if parts:
email = parts[0] email = parts[0]
name = email_from.find(email) != -1 and email_from[:email_from.index(email)].replace('"', '').replace('<', '').strip() or email_from
name = (
email_from.find(email) != -1
and email_from[: email_from.index(email)]
.replace('"', "")
.replace("<", "")
.strip()
or email_from
)
else: else:
name, email = email_from name, email = email_from
res['message_name_from'] = name
res['message_email_from'] = email
res['partner_id'] = message.author_id.id
if message.author_id and self.env.uid not in [u.id for u in message.author_id.user_ids]:
res['filter_by_partner'] = True
if message.author_id and res.get('model'):
res_id = self.env[res['model']].search([], order='id desc', limit=1)
res["message_name_from"] = name
res["message_email_from"] = email
res["partner_id"] = message.author_id.id
if message.author_id and self.env.uid not in [
u.id for u in message.author_id.user_ids
]:
res["filter_by_partner"] = True
if message.author_id and res.get("model"):
res_id = self.env[res["model"]].search([], order="id desc", limit=1)
if res_id: if res_id:
res['res_id'] = res_id[0].id
res["res_id"] = res_id[0].id
config_parameters = self.env['ir.config_parameter']
res['move_followers'] = config_parameters.get_param('mail_relocation_move_followers')
config_parameters = self.env["ir.config_parameter"]
res["move_followers"] = config_parameters.get_param(
"mail_relocation_move_followers"
)
res['uid'] = self.env.uid
res["uid"] = self.env.uid
return res return res
message_id = fields.Many2one('mail.message', string='Message')
message_body = fields.Html(related='message_id.body', string='Message to move', readonly=True)
message_from = fields.Char(related='message_id.email_from', string='From', readonly=True)
message_subject = fields.Char(related='message_id.subject', string='Subject', readonly=True)
message_moved_by_message_id = fields.Many2one('mail.message', related='message_id.moved_by_message_id', string='Moved with', readonly=True)
message_moved_by_user_id = fields.Many2one('res.users', related='message_id.moved_by_user_id', string='Moved by', readonly=True)
message_is_moved = fields.Boolean(string='Is Moved', related='message_id.is_moved', readonly=True)
parent_id = fields.Many2one('mail.message', string='Search by name', )
model = fields.Selection(_model_selection, string='Model')
res_id = fields.Integer(string='Record')
can_move = fields.Boolean('Can move', compute='_compute_can_move')
move_back = fields.Boolean('MOVE TO ORIGIN', help='Move message and submessages to original place')
partner_id = fields.Many2one('res.partner', string='Author')
filter_by_partner = fields.Boolean('Filter Records by partner')
message_id = fields.Many2one("mail.message", string="Message")
message_body = fields.Html(
related="message_id.body", string="Message to move", readonly=True
)
message_from = fields.Char(
related="message_id.email_from", string="From", readonly=True
)
message_subject = fields.Char(
related="message_id.subject", string="Subject", readonly=True
)
message_moved_by_message_id = fields.Many2one(
"mail.message",
related="message_id.moved_by_message_id",
string="Moved with",
readonly=True,
)
message_moved_by_user_id = fields.Many2one(
"res.users",
related="message_id.moved_by_user_id",
string="Moved by",
readonly=True,
)
message_is_moved = fields.Boolean(
string="Is Moved", related="message_id.is_moved", readonly=True
)
parent_id = fields.Many2one("mail.message", string="Search by name",)
model = fields.Selection(_model_selection, string="Model")
res_id = fields.Integer(string="Record")
can_move = fields.Boolean("Can move", compute="_compute_can_move")
move_back = fields.Boolean(
"MOVE TO ORIGIN", help="Move message and submessages to original place"
)
partner_id = fields.Many2one("res.partner", string="Author")
filter_by_partner = fields.Boolean("Filter Records by partner")
message_email_from = fields.Char() message_email_from = fields.Char()
message_name_from = fields.Char() message_name_from = fields.Char()
# FIXME message_to_read should be True even if current message or any his childs are unread # FIXME message_to_read should be True even if current message or any his childs are unread
message_to_read = fields.Boolean(related='message_id.needaction')
message_to_read = fields.Boolean(related="message_id.needaction")
uid = fields.Integer() uid = fields.Integer()
move_followers = fields.Boolean( move_followers = fields.Boolean(
'Move Followers',
"Move Followers",
help="Add followers of current record to a new record.\n" help="Add followers of current record to a new record.\n"
"You must use this option, if new record has restricted access.\n"
"You can change default value for this option at Settings/System Parameters")
"You must use this option, if new record has restricted access.\n"
"You can change default value for this option at Settings/System Parameters",
)
@api.depends('message_id')
@api.depends("message_id")
@api.multi @api.multi
def _compute_can_move(self): def _compute_can_move(self):
for r in self: for r in self:
# message was not moved before OR message is a top message of previous move # message was not moved before OR message is a top message of previous move
r.can_move = not r.message_id.moved_by_message_id or r.message_id.moved_by_message_id.id == r.message_id.id
r.can_move = (
not r.message_id.moved_by_message_id
or r.message_id.moved_by_message_id.id == r.message_id.id
)
@api.onchange('move_back')
@api.onchange("move_back")
def on_change_move_back(self): def on_change_move_back(self):
if not self.move_back: if not self.move_back:
return return
@ -103,14 +144,16 @@ class Wizard(models.TransientModel):
self.model = model self.model = model
self.res_id = self.message_id.moved_from_res_id self.res_id = self.message_id.moved_from_res_id
@api.onchange('parent_id', 'res_id', 'model')
@api.onchange("parent_id", "res_id", "model")
def update_move_back(self): def update_move_back(self):
model = self.message_id.moved_from_model model = self.message_id.moved_from_model
self.move_back = self.parent_id == self.message_id.moved_from_parent_id \
and self.res_id == self.message_id.moved_from_res_id \
self.move_back = (
self.parent_id == self.message_id.moved_from_parent_id
and self.res_id == self.message_id.moved_from_res_id
and (self.model == model or (not self.model and not model)) and (self.model == model or (not self.model and not model))
)
@api.onchange('parent_id')
@api.onchange("parent_id")
def on_change_parent_id(self): def on_change_parent_id(self):
if self.parent_id and self.parent_id.model: if self.parent_id and self.parent_id.model:
self.model = self.parent_id.model self.model = self.parent_id.model
@ -119,24 +162,26 @@ class Wizard(models.TransientModel):
self.model = None self.model = None
self.res_id = None self.res_id = None
@api.onchange('model', 'filter_by_partner', 'partner_id')
@api.onchange("model", "filter_by_partner", "partner_id")
def on_change_partner(self): def on_change_partner(self):
domain = {'res_id': [('id', '!=', self.message_id.res_id)]}
domain = {"res_id": [("id", "!=", self.message_id.res_id)]}
if self.model and self.filter_by_partner and self.partner_id: if self.model and self.filter_by_partner and self.partner_id:
fields = self.env[self.model].fields_get(False) fields = self.env[self.model].fields_get(False)
contact_field = False contact_field = False
for n, f in fields.iteritems(): for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
if f["type"] == "many2one" and f["relation"] == "res.partner":
contact_field = n contact_field = n
break break
if contact_field: if contact_field:
domain['res_id'].append((contact_field, '=', self.partner_id.id))
domain["res_id"].append((contact_field, "=", self.partner_id.id))
if self.model: if self.model:
res_id = self.env[self.model].search(domain['res_id'], order='id desc', limit=1)
res_id = self.env[self.model].search(
domain["res_id"], order="id desc", limit=1
)
self.res_id = res_id and res_id[0].id self.res_id = res_id and res_id[0].id
else: else:
self.res_id = None self.res_id = None
return {'domain': domain}
return {"domain": domain}
@api.multi @api.multi
def check_access(self): def check_access(self):
@ -146,16 +191,18 @@ class Wizard(models.TransientModel):
@api.multi @api.multi
def check_access_one(self): def check_access_one(self):
self.ensure_one() self.ensure_one()
operation = 'write'
operation = "write"
if not (self.model and self.res_id): if not (self.model and self.res_id):
return True return True
model_obj = self.env[self.model] model_obj = self.env[self.model]
mids = model_obj.browse(self.res_id).exists() mids = model_obj.browse(self.res_id).exists()
if hasattr(model_obj, 'check_mail_message_access'):
if hasattr(model_obj, "check_mail_message_access"):
model_obj.check_mail_message_access(mids.ids, operation) model_obj.check_mail_message_access(mids.ids, operation)
else: else:
self.env['mail.thread'].check_mail_message_access(mids.ids, operation, model_name=self.model)
self.env["mail.thread"].check_mail_message_access(
mids.ids, operation, model_name=self.model
)
@api.multi @api.multi
def open_moved_by_message_id(self): def open_moved_by_message_id(self):
@ -163,42 +210,49 @@ class Wizard(models.TransientModel):
for r in self: for r in self:
message_id = r.message_moved_by_message_id.id message_id = r.message_moved_by_message_id.id
return { return {
'type': 'ir.actions.act_window',
'res_model': 'mail_move_message.wizard',
'view_mode': 'form',
'view_type': 'form',
'views': [[False, 'form']],
'target': 'new',
'context': {'default_message_id': message_id},
"type": "ir.actions.act_window",
"res_model": "mail_move_message.wizard",
"view_mode": "form",
"view_type": "form",
"views": [[False, "form"]],
"target": "new",
"context": {"default_message_id": message_id},
} }
@api.multi @api.multi
def move(self): def move(self):
for r in self: for r in self:
r.check_access() r.check_access()
if not r.parent_id or not (r.parent_id.model == r.model and
r.parent_id.res_id == r.res_id):
if not r.parent_id or not (
r.parent_id.model == r.model and r.parent_id.res_id == r.res_id
):
# link with the first message of record # link with the first message of record
parent = self.env['mail.message'].search([('model', '=', r.model), ('res_id', '=', r.res_id)], order='id', limit=1)
parent = self.env["mail.message"].search(
[("model", "=", r.model), ("res_id", "=", r.res_id)],
order="id",
limit=1,
)
r.parent_id = parent.id or None r.parent_id = parent.id or None
r.message_id.move(r.parent_id.id, r.res_id, r.model, r.move_back, r.move_followers)
r.message_id.move(
r.parent_id.id, r.res_id, r.model, r.move_back, r.move_followers
)
if not (r.model and r.res_id): if not (r.model and r.res_id):
r.message_id.needaction = False r.message_id.needaction = False
return { return {
'type': 'ir.actions.client',
'name': 'All messages',
'tag': 'reload',
"type": "ir.actions.client",
"name": "All messages",
"tag": "reload",
} }
return { return {
'name': _('Record'),
'view_type': 'form',
'view_mode': 'form',
'res_model': r.model,
'res_id': r.res_id,
'views': [(False, 'form')],
'type': 'ir.actions.act_window',
"name": _("Record"),
"view_type": "form",
"view_mode": "form",
"res_model": r.model,
"res_id": r.res_id,
"views": [(False, "form")],
"type": "ir.actions.act_window",
} }
@api.multi @api.multi
@ -212,34 +266,40 @@ class Wizard(models.TransientModel):
msg_id = self.message_id.id msg_id = self.message_id.id
# Send notification # Send notification
notification = {'id': msg_id}
self.env['bus.bus'].sendone((self._cr.dbname, 'mail_move_message.delete_message'), notification)
notification = {"id": msg_id}
self.env["bus.bus"].sendone(
(self._cr.dbname, "mail_move_message.delete_message"), notification
)
self.message_id.unlink() self.message_id.unlink()
return {} return {}
@api.model @api.model
def create_partner(self, message_id, relation, partner_id, message_name_from, message_email_from):
def create_partner(
self, message_id, relation, partner_id, message_name_from, message_email_from
):
model = self.env[relation] model = self.env[relation]
message = self.env['mail.message'].browse(message_id)
message = self.env["mail.message"].browse(message_id)
if not partner_id and message_name_from: if not partner_id and message_name_from:
partner_id = self.env['res.partner'].with_context({'update_message_author': True}).create({
'name': message_name_from,
'email': message_email_from
}).id
context = {'partner_id': partner_id}
partner_id = (
self.env["res.partner"]
.with_context({"update_message_author": True})
.create({"name": message_name_from, "email": message_email_from})
.id
)
context = {"partner_id": partner_id}
if model._rec_name: if model._rec_name:
context.update({'default_%s' % model._rec_name: message.subject})
context.update({"default_%s" % model._rec_name: message.subject})
fields = model.fields_get() fields = model.fields_get()
contact_field = False contact_field = False
for n, f in fields.iteritems(): for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
if f["type"] == "many2one" and f["relation"] == "res.partner":
contact_field = n contact_field = n
break break
if contact_field: if contact_field:
context.update({'default_%s' % contact_field: partner_id})
context.update({"default_%s" % contact_field: partner_id})
return context return context
@api.multi @api.multi
@ -252,19 +312,33 @@ class Wizard(models.TransientModel):
self.ensure_one() self.ensure_one()
self.message_id.set_message_done() self.message_id.set_message_done()
self.message_id.child_ids.set_message_done() self.message_id.child_ids.set_message_done()
return {'type': 'ir.actions.act_window_close'}
return {"type": "ir.actions.act_window_close"}
class MailMessage(models.Model): class MailMessage(models.Model):
_inherit = 'mail.message'
is_moved = fields.Boolean('Is moved')
moved_from_res_id = fields.Integer('Related Document ID (Original)')
moved_from_model = fields.Char('Related Document Model (Original)')
moved_from_parent_id = fields.Many2one('mail.message', 'Parent Message (Original)', ondelete='set null')
moved_by_message_id = fields.Many2one('mail.message', 'Moved by message', ondelete='set null', help='Top message, that initate moving this message')
moved_by_user_id = fields.Many2one('res.users', 'Moved by user', ondelete='set null')
all_child_ids = fields.One2many('mail.message', string='All childs', compute='_compute_all_childs', help='all childs, including subchilds')
_inherit = "mail.message"
is_moved = fields.Boolean("Is moved")
moved_from_res_id = fields.Integer("Related Document ID (Original)")
moved_from_model = fields.Char("Related Document Model (Original)")
moved_from_parent_id = fields.Many2one(
"mail.message", "Parent Message (Original)", ondelete="set null"
)
moved_by_message_id = fields.Many2one(
"mail.message",
"Moved by message",
ondelete="set null",
help="Top message, that initate moving this message",
)
moved_by_user_id = fields.Many2one(
"res.users", "Moved by user", ondelete="set null"
)
all_child_ids = fields.One2many(
"mail.message",
string="All childs",
compute="_compute_all_childs",
help="all childs, including subchilds",
)
@api.multi @api.multi
def _compute_all_childs(self, include_myself=True): def _compute_all_childs(self, include_myself=True):
@ -278,27 +352,32 @@ class MailMessage(models.Model):
if include_myself: if include_myself:
ids.append(self.id) ids.append(self.id)
while True: while True:
new_ids = self.search([('parent_id', 'in', ids), ('id', 'not in', ids)]).ids
new_ids = self.search([("parent_id", "in", ids), ("id", "not in", ids)]).ids
if new_ids: if new_ids:
ids = ids + new_ids ids = ids + new_ids
continue continue
break break
moved_childs = self.search([('moved_by_message_id', '=', self.id)]).ids
moved_childs = self.search([("moved_by_message_id", "=", self.id)]).ids
self.all_child_ids = ids + moved_childs self.all_child_ids = ids + moved_childs
@api.multi @api.multi
def move_followers(self, model, ids): def move_followers(self, model, ids):
fol_obj = self.env['mail.followers']
fol_obj = self.env["mail.followers"]
for message in self: for message in self:
followers = fol_obj.sudo().search([('res_model', '=', message.model),
('res_id', '=', message.res_id)])
followers = fol_obj.sudo().search(
[("res_model", "=", message.model), ("res_id", "=", message.res_id)]
)
for f in followers: for f in followers:
self.env[model].browse(ids).message_subscribe([f.partner_id.id], [s.id for s in f.subtype_ids])
self.env[model].browse(ids).message_subscribe(
[f.partner_id.id], [s.id for s in f.subtype_ids]
)
@api.multi @api.multi
def move(self, parent_id, res_id, model, move_back, move_followers=False): def move(self, parent_id, res_id, model, move_back, move_followers=False):
for r in self: for r in self:
r.move_one(parent_id, res_id, model, move_back, move_followers=move_followers)
r.move_one(
parent_id, res_id, model, move_back, move_followers=move_followers
)
@api.multi @api.multi
def move_one(self, parent_id, res_id, model, move_back, move_followers=False): def move_one(self, parent_id, res_id, model, move_back, move_followers=False):
@ -311,128 +390,142 @@ class MailMessage(models.Model):
vals = {} vals = {}
if move_back: if move_back:
# clear variables if we move everything back # clear variables if we move everything back
vals['is_moved'] = False
vals['moved_by_user_id'] = None
vals['moved_by_message_id'] = None
vals["is_moved"] = False
vals["moved_by_user_id"] = None
vals["moved_by_message_id"] = None
vals['moved_from_res_id'] = None
vals['moved_from_model'] = None
vals['moved_from_parent_id'] = None
vals["moved_from_res_id"] = None
vals["moved_from_model"] = None
vals["moved_from_parent_id"] = None
else: else:
vals['parent_id'] = parent_id
vals['res_id'] = res_id
vals['model'] = model
vals["parent_id"] = parent_id
vals["res_id"] = res_id
vals["model"] = model
vals['is_moved'] = True
vals['moved_by_user_id'] = self.env.user.id
vals['moved_by_message_id'] = self.id
vals["is_moved"] = True
vals["moved_by_user_id"] = self.env.user.id
vals["moved_by_message_id"] = self.id
# Update record_name in message # Update record_name in message
vals['record_name'] = self._get_record_name(vals)
vals["record_name"] = self._get_record_name(vals)
for r in self.all_child_ids: for r in self.all_child_ids:
r_vals = vals.copy() r_vals = vals.copy()
if not r.is_moved: if not r.is_moved:
# moved_from_* variables contain not last, but original # moved_from_* variables contain not last, but original
# reference # reference
r_vals['moved_from_parent_id'] = r.parent_id.id
r_vals['moved_from_res_id'] = r.res_id
r_vals['moved_from_model'] = r.model
r_vals["moved_from_parent_id"] = r.parent_id.id
r_vals["moved_from_res_id"] = r.res_id
r_vals["moved_from_model"] = r.model
elif move_back: elif move_back:
r_vals['parent_id'] = r.moved_from_parent_id.id
r_vals['res_id'] = r.moved_from_res_id
r_vals['model'] = r.moved_from_model
r_vals["parent_id"] = r.moved_from_parent_id.id
r_vals["res_id"] = r.moved_from_res_id
r_vals["model"] = r.moved_from_model
if move_followers: if move_followers:
r.sudo().move_followers(r_vals.get('model'), r_vals.get('res_id'))
r.sudo().move_followers(r_vals.get("model"), r_vals.get("res_id"))
r.sudo().write(r_vals) r.sudo().write(r_vals)
r.attachment_ids.sudo().write({
'res_id': r_vals.get('res_id'),
'res_model': r_vals.get('model')
})
r.attachment_ids.sudo().write(
{"res_id": r_vals.get("res_id"), "res_model": r_vals.get("model")}
)
# Send notification # Send notification
notification = { notification = {
'id': self.id,
'res_id': vals.get('res_id'),
'model': vals.get('model'),
'is_moved': vals['is_moved'],
'record_name': vals['record_name']
"id": self.id,
"res_id": vals.get("res_id"),
"model": vals.get("model"),
"is_moved": vals["is_moved"],
"record_name": vals["record_name"],
} }
self.env['bus.bus'].sendone((self._cr.dbname, 'mail_move_message'), notification)
self.env["bus.bus"].sendone(
(self._cr.dbname, "mail_move_message"), notification
)
@api.multi @api.multi
def name_get(self): def name_get(self):
context = self.env.context context = self.env.context
if not (context or {}).get('extended_name'):
if not (context or {}).get("extended_name"):
return super(MailMessage, self).name_get() return super(MailMessage, self).name_get()
reads = self.read(['record_name', 'model', 'res_id'])
reads = self.read(["record_name", "model", "res_id"])
res = [] res = []
for record in reads: for record in reads:
name = record['record_name'] or ''
extended_name = ' [%s] ID %s' % (record.get('model', 'UNDEF'), record.get('res_id', 'UNDEF'))
res.append((record['id'], name + extended_name))
name = record["record_name"] or ""
extended_name = " [{}] ID {}".format(
record.get("model", "UNDEF"), record.get("res_id", "UNDEF"),
)
res.append((record["id"], name + extended_name))
return res return res
@api.multi @api.multi
def message_format(self): def message_format(self):
message_values = super(MailMessage, self).message_format() message_values = super(MailMessage, self).message_format()
message_index = {message['id']: message for message in message_values}
message_index = {message["id"]: message for message in message_values}
for item in self: for item in self:
msg = message_index.get(item.id) msg = message_index.get(item.id)
if msg: if msg:
msg['is_moved'] = item.is_moved
msg["is_moved"] = item.is_moved
return message_values return message_values
class MailMoveMessageConfiguration(models.TransientModel): class MailMoveMessageConfiguration(models.TransientModel):
_name = 'mail_move_message.config.settings'
_inherit = 'res.config.settings'
_name = "mail_move_message.config.settings"
_inherit = "res.config.settings"
model_ids = fields.Many2many(comodel_name='ir.model', string='Models')
move_followers = fields.Boolean('Move Followers')
model_ids = fields.Many2many(comodel_name="ir.model", string="Models")
move_followers = fields.Boolean("Move Followers")
@api.model @api.model
def get_default_move_message_configs(self, fields): def get_default_move_message_configs(self, fields):
config_parameters = self.env['ir.config_parameter']
model_obj = self.env['ir.model']
model_names = config_parameters.get_param('mail_relocation_models')
config_parameters = self.env["ir.config_parameter"]
model_obj = self.env["ir.model"]
model_names = config_parameters.get_param("mail_relocation_models")
if not model_names: if not model_names:
return {} return {}
model_names = model_names.split(',')
model_ids = model_obj.search([('model', 'in', model_names)])
model_names = model_names.split(",")
model_ids = model_obj.search([("model", "in", model_names)])
return { return {
'model_ids': [m.id for m in model_ids],
'move_followers': config_parameters.get_param('mail_relocation_move_followers')
"model_ids": [m.id for m in model_ids],
"move_followers": config_parameters.get_param(
"mail_relocation_move_followers"
),
} }
@api.multi @api.multi
def set_move_message_configs(self): def set_move_message_configs(self):
config_parameters = self.env['ir.config_parameter']
model_names = ''
config_parameters = self.env["ir.config_parameter"]
model_names = ""
for record in self: for record in self:
model_names = ','.join([m.model for m in record.model_ids])
config_parameters.set_param('mail_relocation_models', model_names)
config_parameters.set_param('mail_relocation_move_followers', record.move_followers or '')
model_names = ",".join([m.model for m in record.model_ids])
config_parameters.set_param("mail_relocation_models", model_names)
config_parameters.set_param(
"mail_relocation_move_followers", record.move_followers or ""
)
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
@api.model @api.model
def create(self, vals): def create(self, vals):
res = super(ResPartner, self).create(vals) res = super(ResPartner, self).create(vals)
if 'update_message_author' in self.env.context and 'email' in vals:
mail_message_obj = self.env['mail.message']
if "update_message_author" in self.env.context and "email" in vals:
mail_message_obj = self.env["mail.message"]
# Escape special SQL characters in email_address to avoid invalid matches # Escape special SQL characters in email_address to avoid invalid matches
email_address = (vals['email'].replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_'))
email_address = (
vals["email"]
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")
)
email_brackets = "<%s>" % email_address email_brackets = "<%s>" % email_address
messages = mail_message_obj.search([
'|',
('email_from', '=ilike', email_address),
('email_from', 'ilike', email_brackets),
('author_id', '=', False)
])
messages = mail_message_obj.search(
[
"|",
("email_from", "=ilike", email_address),
("email_from", "ilike", email_brackets),
("author_id", "=", False),
]
)
if messages: if messages:
messages.sudo().write({'author_id': res.id})
messages.sudo().write({"author_id": res.id})
return res return res

170
mail_move_message/mail_move_message_views.xml

@ -1,10 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp><data> <openerp><data>
<template id="assets_backend" name="custom bar assets" inherit_id="web.assets_backend"> <template id="assets_backend" name="custom bar assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" href="/mail_move_message/static/src/css/mail_move_message.css"/>
<script type="text/javascript" src="/mail_move_message/static/src/js/mail_move_message.js"></script>
<link
rel="stylesheet"
href="/mail_move_message/static/src/css/mail_move_message.css"
/>
<script
type="text/javascript"
src="/mail_move_message/static/src/js/mail_move_message.js"
/>
</xpath> </xpath>
</template> </template>
@ -14,54 +20,98 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Move Message"> <form string="Move Message">
<field name="can_move" invisible="1"/>
<field name="message_is_moved" invisible="1"/>
<field name="message_name_from" invisible="1"/>
<field name="message_email_from" invisible="1"/>
<field name="message_to_read" invisible="1"/>
<field name="uid" invisible="1"/>
<field name="can_move" invisible="1" />
<field name="message_is_moved" invisible="1" />
<field name="message_name_from" invisible="1" />
<field name="message_email_from" invisible="1" />
<field name="message_to_read" invisible="1" />
<field name="uid" invisible="1" />
<p attrs="{'invisible':[('can_move', '!=', False)]}">You cannot move this message. It was already moved with a message bellow. Open one and apply changes there.</p>
<p
attrs="{'invisible':[('can_move', '!=', False)]}"
>You cannot move this message. It was already moved with a message bellow. Open one and apply changes there.</p>
<group attrs="{'invisible':[('can_move', '!=', False)]}"> <group attrs="{'invisible':[('can_move', '!=', False)]}">
<field name="message_moved_by_message_id" context="{'extended_name':1}"/>
<field name="message_moved_by_user_id"/>
<button name="open_moved_by_message_id" string="Open message" type="object" class="oe_highlight"/>
<field
name="message_moved_by_message_id"
context="{'extended_name':1}"
/>
<field name="message_moved_by_user_id" />
<button
name="open_moved_by_message_id"
string="Open message"
type="object"
class="oe_highlight"
/>
</group> </group>
<group attrs="{'invisible':[('can_move', '=', False)]}" colspan="2"> <group attrs="{'invisible':[('can_move', '=', False)]}" colspan="2">
<label for="model"/>
<label for="model" />
<div> <div>
<field name="model" widget="polymorphic" polymorphic="res_id" class="oe_inline"/>
<field
name="model"
widget="polymorphic"
polymorphic="res_id"
class="oe_inline"
/>
</div> </div>
<label for="filter_by_partner"/>
<label for="filter_by_partner" />
<div> <div>
<field name="filter_by_partner" class="oe_inline"/>
<field name="partner_id" class="oe_inline"/>
<button string="Create Partner" attrs="{'invisible':[('partner_id','!=',False)]}"
class="oe_highlight oe_inline ml32"
special="quick_create" field="partner_id" context="{'force_email':True,'default_email':message_email_from,'default_name':message_name_from, 'update_message_author':True}" />
<field name="filter_by_partner" class="oe_inline" />
<field name="partner_id" class="oe_inline" />
<button
string="Create Partner"
attrs="{'invisible':[('partner_id','!=',False)]}"
class="oe_highlight oe_inline ml32"
special="quick_create"
field="partner_id"
context="{'force_email':True,'default_email':message_email_from,'default_name':message_name_from, 'update_message_author':True}"
/>
</div> </div>
<label for="res_id"/>
<label for="res_id" />
<div> <div>
<field name="res_id" context="{'extended_name_with_contact':1}" widget="many2one" attrs="{'readonly': [('model','=',False)]}" class="oe_inline"/>
<button string="Create new record" name="create_record" type="object" class="oe_highlight oe_inline ml32" attrs="{'invisible':['|',('model','=',False)]}" special="quick_create" field="res_id" use_for_mail_move_message="True"/>
<field
name="res_id"
context="{'extended_name_with_contact':1}"
widget="many2one"
attrs="{'readonly': [('model','=',False)]}"
class="oe_inline"
/>
<button
string="Create new record"
name="create_record"
type="object"
class="oe_highlight oe_inline ml32"
attrs="{'invisible':['|',('model','=',False)]}"
special="quick_create"
field="res_id"
use_for_mail_move_message="True"
/>
</div> </div>
<label for="move_back" attrs="{'invisible':[('message_is_moved','=',False)]}"/>
<label
for="move_back"
attrs="{'invisible':[('message_is_moved','=',False)]}"
/>
<div attrs="{'invisible':[('message_is_moved','=',False)]}"> <div attrs="{'invisible':[('message_is_moved','=',False)]}">
<field name="move_back"/>
<field name="move_back" />
</div> </div>
<label for="move_followers"/>
<label for="move_followers" />
<div> <div>
<field name="move_followers"/>
<field name="move_followers" />
</div> </div>
</group> </group>
<button name="move" string="Move" type="object" class="oe_highlight" attrs="{'invisible':[('can_move', '=', False)]}"/>
<button
name="move"
string="Move"
type="object"
class="oe_highlight"
attrs="{'invisible':[('can_move', '=', False)]}"
/>
<button string="Close" class="" special="cancel" /> <button string="Close" class="" special="cancel" />
<separator string="Message"/>
<separator string="Message" />
<group> <group>
<field name="message_subject"/>
<field name="message_from"/>
<field name="message_id" invisible="1"/>
<field name="message_subject" />
<field name="message_from" />
<field name="message_id" invisible="1" />
</group> </group>
<div class="openerp mail_move_message"> <div class="openerp mail_move_message">
<div class="oe_mail"> <div class="oe_mail">
@ -69,18 +119,30 @@
<div class="oe_msg_content"> <div class="oe_msg_content">
<div class="oe_msg_body"> <div class="oe_msg_body">
<!-- use built-in css for messages --> <!-- use built-in css for messages -->
<field name="message_body"/>
<field name="message_body" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<footer> <footer>
<button name="read_close" string="Mark as read and Close" type="object" class="oe_highlight"/> or
<button special="cancel" string="Close" class="oe_link"/>
<button
name="read_close"
string="Mark as read and Close"
type="object"
class="oe_highlight"
/> or
<button special="cancel" string="Close" class="oe_link" />
<button name="delete" string="Delete message" type="object" class="oe_highlight pull-right" confirm="Do you really want to delete this message?" attrs="{'invisible':[('uid','!=',1)]}"/>
<button
name="delete"
string="Delete message"
type="object"
class="oe_highlight pull-right"
confirm="Do you really want to delete this message?"
attrs="{'invisible':[('uid','!=',1)]}"
/>
</footer> </footer>
</form> </form>
</field> </field>
@ -93,16 +155,26 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Configure Mail Relocation" class="oe_form_configuration"> <form string="Configure Mail Relocation" class="oe_form_configuration">
<header> <header>
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
<button
string="Apply"
type="object"
name="execute"
class="oe_highlight"
/>
or or
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
<button
string="Cancel"
type="object"
name="cancel"
class="oe_link"
/>
</header> </header>
<div name="general"> <div name="general">
<separator string="Models"/>
<field name="model_ids" widget="many2many_tags"/>
<separator string="Options"/>
<label for="move_followers"/>
<field name="move_followers"/>
<separator string="Models" />
<field name="model_ids" widget="many2many_tags" />
<separator string="Options" />
<label for="move_followers" />
<field name="move_followers" />
</div> </div>
</form> </form>
</field> </field>
@ -112,13 +184,19 @@
<field name="name">Mail Relocation</field> <field name="name">Mail Relocation</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">mail_move_message.config.settings</field> <field name="res_model">mail_move_message.config.settings</field>
<field name="view_id" ref="view_mail_move_message_config_settings"/>
<field name="view_id" ref="view_mail_move_message_config_settings" />
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="target">inline</field> <field name="target">inline</field>
</record> </record>
<!-- Add menu entry in Settings/Email --> <!-- Add menu entry in Settings/Email -->
<menuitem name="Mail Relocation" id="menu_mail_move_message" parent="base.menu_email" sequence="99" action="action_mail_move_message_config"/>
<menuitem
name="Mail Relocation"
id="menu_mail_move_message"
parent="base.menu_email"
sequence="99"
action="action_mail_move_message_config"
/>
</data> </data>
</openerp> </openerp>

5
mail_move_message/static/src/css/mail_move_message.css

@ -1,6 +1,7 @@
i.oe_moved { i.oe_moved {
color: #ED6F6A;
text-shadow: 0px 1px #961b1b,0px -1px #961b1b, -1px 0px #961b1b, 1px 0px #961b1b, 0px 3px 3px rgba(0,0,0,0.1);
color: #ed6f6a;
text-shadow: 0px 1px #961b1b, 0px -1px #961b1b, -1px 0px #961b1b, 1px 0px #961b1b,
0px 3px 3px rgba(0, 0, 0, 0.1);
} }
.mail_move_message { .mail_move_message {
width: 864px; width: 864px;

128
mail_move_message/static/src/js/mail_move_message.js

@ -1,80 +1,84 @@
odoo.define('mail_move_message.relocate', function (require) {
odoo.define("mail_move_message.relocate", function(require) {
"use strict"; "use strict";
var bus = require('bus.bus').bus;
var chat_manager = require('mail.chat_manager');
var base_obj = require('mail_base.base');
var thread = require('mail.ChatThread');
var chatter = require('mail.Chatter');
var Model = require('web.Model');
var form_common = require('web.form_common');
var widgets = require('web.form_widgets');
var core = require('web.core');
var chat_manager = require("mail.chat_manager");
var base_obj = require("mail_base.base");
var thread = require("mail.ChatThread");
var chatter = require("mail.Chatter");
var Model = require("web.Model");
var form_common = require("web.form_common");
var widgets = require("web.form_widgets");
var core = require("web.core");
var _t = core._t; var _t = core._t;
thread.include({ thread.include({
init: function(){
init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
// Add click reaction in the events of the thread object // Add click reaction in the events of the thread object
this.events['click .oe_move'] = function(event) {
var message_id = $(event.currentTarget).data('message-id');
this.events["click .oe_move"] = function(event) {
var message_id = $(event.currentTarget).data("message-id");
this.trigger("move_message", message_id); this.trigger("move_message", message_id);
}; };
}, },
on_move_message: function(message_id){
on_move_message: function(message_id) {
var action = { var action = {
name: _t('Relocate Message'),
type: 'ir.actions.act_window',
res_model: 'mail_move_message.wizard',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: {'default_message_id': message_id}
name: _t("Relocate Message"),
type: "ir.actions.act_window",
res_model: "mail_move_message.wizard",
view_mode: "form",
view_type: "form",
views: [[false, "form"]],
target: "new",
context: {default_message_id: message_id},
}; };
this.do_action(action, { this.do_action(action, {
'on_close': function(){}
on_close: function() {
// Empty
},
}); });
}
},
}); });
chatter.include({ chatter.include({
start: function() { start: function() {
var result = this._super.apply(this, arguments); var result = this._super.apply(this, arguments);
// For show wizard in the form // For show wizard in the form
this.thread.on('move_message', this, this.thread.on_move_message);
return $.when(result).done(function() {});
}
this.thread.on("move_message", this, this.thread.on_move_message);
return $.when(result).done(function() {
// TODO: do we need .done(...) part?
});
},
}); });
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({ ChatAction.include({
start: function() { start: function() {
var result = this._super.apply(this, arguments); var result = this._super.apply(this, arguments);
// For show wizard in the channels // For show wizard in the channels
this.thread.on('move_message', this, this.thread.on_move_message);
return $.when(result).done(function() {});
}
this.thread.on("move_message", this, this.thread.on_move_message);
return $.when(result).done(function() {
// TODO: do we need .done(...) part?
});
},
}); });
base_obj.MailTools.include({ base_obj.MailTools.include({
make_message: function(data){
make_message: function(data) {
var msg = this._super(data); var msg = this._super(data);
// Mark msg as moved after reload // Mark msg as moved after reload
msg.is_moved = data.is_moved || false; msg.is_moved = data.is_moved || false;
return msg; return msg;
}, },
on_notification: function(notifications){
on_notification: function(notifications) {
this._super(notifications); this._super(notifications);
var self = this; var self = this;
_.each(notifications, function (notification) {
_.each(notifications, function(notification) {
var model = notification[0][1]; var model = notification[0][1];
var message_id = notification[1].id; var message_id = notification[1].id;
var message = base_obj.chat_manager.get_message(message_id); var message = base_obj.chat_manager.get_message(message_id);
if (model === 'mail_move_message' && message) {
if (model === "mail_move_message" && message) {
message.res_id = notification[1].res_id; message.res_id = notification[1].res_id;
message.model = notification[1].model; message.model = notification[1].model;
message.record_name = notification[1].record_name; message.record_name = notification[1].record_name;
@ -83,62 +87,62 @@ odoo.define('mail_move_message.relocate', function (require) {
// Update cache and accordingly message in the thread // Update cache and accordingly message in the thread
self.add_to_cache(message, []); self.add_to_cache(message, []);
// Call thread.on_update_message(message) // Call thread.on_update_message(message)
chat_manager.bus.trigger('update_message', message);
} else if (model === 'mail_move_message.delete_message') {
chat_manager.bus.trigger("update_message", message);
} else if (model === "mail_move_message.delete_message") {
self.remove_from_cache(message, []); self.remove_from_cache(message, []);
chat_manager.bus.trigger('update_message', message);
chat_manager.bus.trigger("update_message", message);
} }
}); });
}
},
}); });
widgets.WidgetButton.include({ widgets.WidgetButton.include({
on_click: function(){
if(this.node.attrs.special == 'quick_create'){
on_click: function() {
if (this.node.attrs.special === "quick_create") {
var self = this; var self = this;
var related_field = this.field_manager.fields[this.node.attrs.field]; var related_field = this.field_manager.fields[this.node.attrs.field];
var context_built = $.Deferred(); var context_built = $.Deferred();
if(this.node.attrs.use_for_mail_move_message) {
if (this.node.attrs.use_for_mail_move_message) {
var model = new Model(this.view.dataset.model); var model = new Model(this.view.dataset.model);
var partner_id = self.field_manager.fields.partner_id.get_value(); var partner_id = self.field_manager.fields.partner_id.get_value();
var message_name_from = self.field_manager.fields.message_name_from.get_value(); var message_name_from = self.field_manager.fields.message_name_from.get_value();
var message_email_from = self.field_manager.fields.message_email_from.get_value(); var message_email_from = self.field_manager.fields.message_email_from.get_value();
context_built = model.call('create_partner', [
self.view.dataset.context.default_message_id,
related_field.field.relation,
partner_id,
message_name_from,
message_email_from
]);
}
else {
context_built = model.call("create_partner", [
self.view.dataset.context.default_message_id,
related_field.field.relation,
partner_id,
message_name_from,
message_email_from,
]);
} else {
context_built.resolve(this.build_context()); context_built.resolve(this.build_context());
} }
$.when(context_built).pipe(function (context) {
if(self.node.attrs.use_for_mail_move_message) {
self.field_manager.fields.partner_id.set_value(context.partner_id);
$.when(context_built).pipe(function(context) {
if (self.node.attrs.use_for_mail_move_message) {
self.field_manager.fields.partner_id.set_value(
context.partner_id
);
} }
var dialog = new form_common.FormViewDialog(self, { var dialog = new form_common.FormViewDialog(self, {
res_model: related_field.field.relation, res_model: related_field.field.relation,
res_id: false, res_id: false,
context: context, context: context,
title: _t("Create new record")
title: _t("Create new record"),
}).open(); }).open();
dialog.on('closed', self, function () {
dialog.on("closed", self, function() {
self.force_disabled = false; self.force_disabled = false;
self.check_disable(); self.check_disable();
}); });
dialog.on('create_completed', self, function(id) {
dialog.on("create_completed", self, function(id) {
related_field.set_value(id); related_field.set_value(id);
if(self.field_manager.fields.filter_by_partner) {
if (self.field_manager.fields.filter_by_partner) {
self.field_manager.fields.filter_by_partner.set_value(true); self.field_manager.fields.filter_by_partner.set_value(true);
} }
}); });
}); });
}
else {
} else {
this._super.apply(this, arguments); this._super.apply(this, arguments);
} }
}
},
}); });
}); });

10
mail_move_message/static/src/xml/mail_move_message_main.xml

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template> <template>
<t t-extend="mail.ChatThread.Message"> <t t-extend="mail.ChatThread.Message">
<t t-jquery='p.o_mail_info>span>i:first-child' t-operation="before"> <t t-jquery='p.o_mail_info>span>i:first-child' t-operation="before">
<i t-if="!message.is_system_notification" t-att-class="'fa fa-exchange oe_move' + (message.is_moved ? ' oe_moved' : '')"
t-att-data-message-id="message.id" title="Move to thread"/>
<i
t-if="!message.is_system_notification"
t-att-class="'fa fa-exchange oe_move' + (message.is_moved ? ' oe_moved' : '')"
t-att-data-message-id="message.id"
title="Move to thread"
/>
</t> </t>
</t> </t>
</template> </template>

21
mail_private/__manifest__.py

@ -11,36 +11,23 @@
"name": """Internal Messaging""", "name": """Internal Messaging""",
"summary": """Send private messages to specified recipients, regardless of who are in followers list.""", "summary": """Send private messages to specified recipients, regardless of who are in followers list.""",
"category": "Discuss", "category": "Discuss",
"images": ['images/mail_private_image.png'],
"images": ["images/mail_private_image.png"],
"version": "10.0.1.1.0", "version": "10.0.1.1.0",
"application": False, "application": False,
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "GPL-3", "license": "GPL-3",
"price": 50.00, "price": 50.00,
"currency": "EUR", "currency": "EUR",
"depends": [
"mail",
"base",
"mail_base"
],
"depends": ["mail", "base", "mail_base"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
'template.xml',
'full_composer_wizard.xml',
],
"qweb": [
'static/src/xml/mail_private.xml',
],
"data": ["template.xml", "full_composer_wizard.xml"],
"qweb": ["static/src/xml/mail_private.xml"],
"demo": [], "demo": [],
"post_load": None, "post_load": None,
"pre_init_hook": None, "pre_init_hook": None,
"post_init_hook": None, "post_init_hook": None,
"auto_install": False, "auto_install": False,
"installable": True, "installable": True,
} }

16
mail_private/full_composer_wizard.xml

@ -1,15 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--Copyright 2017 Artyom Losev <https://github.com/ArtyomLosev> <!--Copyright 2017 Artyom Losev <https://github.com/ArtyomLosev>
Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).--> License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<odoo> <odoo>
<record model="ir.ui.view" id="email_compose_message_wizard_form_private"> <record model="ir.ui.view" id="email_compose_message_wizard_form_private">
<field name="name">mail.compose.message.form.private</field> <field name="name">mail.compose.message.form.private</field>
<field name="model">mail.compose.message</field> <field name="model">mail.compose.message</field>
<field name="groups_id" eval="[(4,ref('base.group_user'))]"/>
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
<field name="groups_id" eval="[(4,ref('base.group_user'))]" />
<field name="inherit_id" ref="mail.email_compose_message_wizard_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<data> <data>
@ -17,8 +16,13 @@
<field name="is_private" invisible="1" /> <field name="is_private" invisible="1" />
</xpath> </xpath>
<xpath expr="//div[@groups='base.group_user']/span[2]" position="attributes">
<attribute name="attrs">{'invisible': [('is_private', '=', True)]}
<xpath
expr="//div[@groups='base.group_user']/span[2]"
position="attributes"
>
<attribute
name="attrs"
>{'invisible': [('is_private', '=', True)]}
</attribute> </attribute>
</xpath> </xpath>

85
mail_private/models.py

@ -5,16 +5,16 @@
# Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/> # Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
# License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).
from odoo import models, fields, api
from odoo import api, fields, models
class MailComposeMessage(models.TransientModel): class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
_inherit = "mail.compose.message"
is_private = fields.Boolean(string='Send Internal Message')
is_private = fields.Boolean(string="Send Internal Message")
def get_internal_users_ids(self): def get_internal_users_ids(self):
internal_users_ids = self.env['res.users'].search([('share', '=', False)]).ids
internal_users_ids = self.env["res.users"].search([("share", "=", False)]).ids
return internal_users_ids return internal_users_ids
@api.multi @api.multi
@ -25,18 +25,25 @@ class MailComposeMessage(models.TransientModel):
class MailMessage(models.Model): class MailMessage(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
@api.multi @api.multi
def _notify(self, force_send=False, send_after_commit=True, user_signature=True): def _notify(self, force_send=False, send_after_commit=True, user_signature=True):
self_sudo = self.sudo() self_sudo = self.sudo()
if 'is_private' not in self_sudo._context or not self_sudo._context['is_private']:
super(MailMessage, self)._notify(force_send, send_after_commit, user_signature)
if (
"is_private" not in self_sudo._context
or not self_sudo._context["is_private"]
):
super(MailMessage, self)._notify(
force_send, send_after_commit, user_signature
)
else: else:
self._notify_mail_private(force_send, send_after_commit, user_signature) self._notify_mail_private(force_send, send_after_commit, user_signature)
@api.multi @api.multi
def _notify_mail_private(self, force_send=False, send_after_commit=True, user_signature=True):
def _notify_mail_private(
self, force_send=False, send_after_commit=True, user_signature=True
):
""" The method was partially copied from Odoo. """ The method was partially copied from Odoo.
In the current method, the way of getting channels for a private message is changed. In the current method, the way of getting channels for a private message is changed.
""" """
@ -46,22 +53,33 @@ class MailMessage(models.Model):
# TDE CHECK: add partners / channels as arguments to be able to notify a message with / without computation ?? # TDE CHECK: add partners / channels as arguments to be able to notify a message with / without computation ??
self.ensure_one() # tde: not sure, just for testinh, will see self.ensure_one() # tde: not sure, just for testinh, will see
partners = self.env['res.partner'] | self.partner_ids
channels = self.env['mail.channel'] | self.channel_ids
partners = self.env["res.partner"] | self.partner_ids
channels = self.env["mail.channel"] | self.channel_ids
# update message, with maybe custom values # update message, with maybe custom values
message_values = { message_values = {
'channel_ids': [(6, 0, channels.ids)],
'needaction_partner_ids': [(6, 0, partners.ids)]
"channel_ids": [(6, 0, channels.ids)],
"needaction_partner_ids": [(6, 0, partners.ids)],
} }
if self.model and self.res_id and hasattr(self.env[self.model], 'message_get_message_notify_values'):
if (
self.model
and self.res_id
and hasattr(self.env[self.model], "message_get_message_notify_values")
):
message_values.update( message_values.update(
self.env[self.model].browse(self.res_id).message_get_message_notify_values(self, message_values))
self.env[self.model]
.browse(self.res_id)
.message_get_message_notify_values(self, message_values)
)
self.write(message_values) self.write(message_values)
# notify partners and channels # notify partners and channels
partners._notify(self, force_send=force_send, send_after_commit=send_after_commit,
user_signature=user_signature)
partners._notify(
self,
force_send=force_send,
send_after_commit=send_after_commit,
user_signature=user_signature,
)
channels._notify(self) channels._notify(self)
# Discard cache, because child / parent allow reading and therefore # Discard cache, because child / parent allow reading and therefore
@ -73,15 +91,30 @@ class MailMessage(models.Model):
class MailThread(models.AbstractModel): class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
_inherit = "mail.thread"
@api.multi @api.multi
@api.returns('self', lambda value: value.id)
def message_post(self, body='', subject=None, message_type='notification',
subtype=None, parent_id=False, attachments=None,
content_subtype='html', **kwargs):
if 'channel_ids' in kwargs:
kwargs['channel_ids'] = [(4, pid) for pid in kwargs['channel_ids']]
return super(MailThread, self).message_post(body, subject, message_type,
subtype, parent_id, attachments,
content_subtype, **kwargs)
@api.returns("self", lambda value: value.id)
def message_post(
self,
body="",
subject=None,
message_type="notification",
subtype=None,
parent_id=False,
attachments=None,
content_subtype="html",
**kwargs
):
if "channel_ids" in kwargs:
kwargs["channel_ids"] = [(4, pid) for pid in kwargs["channel_ids"]]
return super(MailThread, self).message_post(
body,
subject,
message_type,
subtype,
parent_id,
attachments,
content_subtype,
**kwargs
)

479
mail_private/static/src/js/mail_private.js

@ -4,267 +4,294 @@
Copyright 2017 Artyom Losev <https://github.com/ArtyomLosev> Copyright 2017 Artyom Losev <https://github.com/ArtyomLosev>
Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/> Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html). */ License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html). */
odoo.define('mail_private', function (require) {
'use strict';
odoo.define("mail_private", function(require) {
"use strict";
var core = require('web.core');
var Chatter = require('mail.Chatter');
var MailComposer = require('mail_base.base').MailComposer;
var chat_manager = require('mail.chat_manager');
var session = require('web.session');
var Model = require('web.Model');
var config = require('web.config');
var utils = require('mail.utils');
var Chatter = require("mail.Chatter");
var MailComposer = require("mail_base.base").MailComposer;
var chat_manager = require("mail.chat_manager");
var session = require("web.session");
var Model = require("web.Model");
var utils = require("mail.utils");
Chatter.include({
init: function() {
this._super.apply(this, arguments);
this.private = false;
this.events["click .oe_compose_post_private"] =
"on_open_composer_private_message";
},
Chatter.include({
init: function () {
this._super.apply(this, arguments);
this.private = false;
this.events['click .oe_compose_post_private'] = 'on_open_composer_private_message';
},
on_post_message: function (message) {
var self = this;
if (this.private) {
message.subtype = false;
message.channel_ids = this.get_checked_channels_ids();
}
var options = {model: this.model, res_id: this.res_id};
chat_manager.post_message(message, options).then(
function () {
self.close_composer();
if (message.partner_ids.length) {
self.refresh_followers();
}
}).fail(function () {
// todo: display notification
});
},
on_post_message: function(message) {
var self = this;
if (this.private) {
message.subtype = false;
message.channel_ids = this.get_checked_channels_ids();
}
var options = {model: this.model, res_id: this.res_id};
chat_manager
.post_message(message, options)
.then(function() {
self.close_composer();
if (message.partner_ids.length) {
self.refresh_followers();
}
})
.fail(function() {
// Todo: display notification
});
},
on_open_composer_private_message: function (event) {
var self = this;
this.private = true;
this.get_recipients_for_internal_message().then(function (data) {
self.recipients_for_internal_message = data;
return self.get_channels_for_internal_message();
}).then(function (data) {
self.channels_for_internal_message = data;
self.get_internal_users_ids().then(function(res_ids){
self.open_composer({is_private: true, internal_ids: res_ids});
});
});
},
on_open_composer_private_message: function(event) {
var self = this;
this.private = true;
this.get_recipients_for_internal_message()
.then(function(data) {
self.recipients_for_internal_message = data;
return self.get_channels_for_internal_message();
})
.then(function(data) {
self.channels_for_internal_message = data;
self.get_internal_users_ids().then(function(res_ids) {
self.open_composer({is_private: true, internal_ids: res_ids});
});
});
},
on_open_composer_new_message: function () {
this._super.apply(this, arguments);
this.private = false;
this.context.is_private = false;
},
on_open_composer_new_message: function() {
this._super.apply(this, arguments);
this.private = false;
this.context.is_private = false;
},
open_composer: function (options) {
var self = this;
this._super.apply(this, arguments);
if (options && options.is_private) {
self.internal_users_ids = options.internal_ids;
this.composer.options.is_private = options.is_private;
open_composer: function(options) {
var self = this;
this._super.apply(this, arguments);
if (options && options.is_private) {
self.internal_users_ids = options.internal_ids;
this.composer.options.is_private = options.is_private;
_.each(self.recipients_for_internal_message, function (partner) {
self.composer.suggested_partners.push({
checked: _.intersection(self.internal_users_ids, partner.user_ids).length > 0,
partner_id: partner.id,
full_name: partner.name,
name: partner.name,
email_address: partner.email,
reason: _.include(partner.user_ids, self.session.uid)
?'Partner'
:'Follower'
_.each(self.recipients_for_internal_message, function(partner) {
self.composer.suggested_partners.push({
checked:
_.intersection(self.internal_users_ids, partner.user_ids)
.length > 0,
partner_id: partner.id,
full_name: partner.name,
name: partner.name,
email_address: partner.email,
reason: _.include(partner.user_ids, self.session.uid)
? "Partner"
: "Follower",
});
}); });
});
_.each(self.channels_for_internal_message, function (channel) {
self.composer.suggested_channels.push({
checked: true,
channel_id: channel.id,
full_name: channel.name,
name: ('# ' + channel.name),
_.each(self.channels_for_internal_message, function(channel) {
self.composer.suggested_channels.push({
checked: true,
channel_id: channel.id,
full_name: channel.name,
name: "# " + channel.name,
});
}); });
});
}
},
}
},
get_recipients_for_internal_message: function () {
var self = this;
self.result = {};
return new Model(this.context.default_model).query(
['message_follower_ids', 'partner_id']).filter(
[['id', '=', self.context.default_res_id]]).all().
then(function (thread) {
var follower_ids = thread[0].message_follower_ids;
self.result[self.context.default_res_id] = [];
self.customer = thread[0].partner_id;
get_recipients_for_internal_message: function() {
var self = this;
self.result = {};
return new Model(this.context.default_model)
.query(["message_follower_ids", "partner_id"])
.filter([["id", "=", self.context.default_res_id]])
.all()
.then(function(thread) {
var follower_ids = thread[0].message_follower_ids;
self.result[self.context.default_res_id] = [];
self.customer = thread[0].partner_id;
// Fetch partner ids
return new Model('mail.followers').call(
'read', [follower_ids, ['partner_id']]).then(function (res_partners) {
// Filter result and push to array
var res_partners_filtered = _.map(res_partners, function (partner) {
if (partner.partner_id[0] && partner.partner_id[0] !== session.partner_id ) {
return partner.partner_id[0];
}
}).filter(function (partner) {
return typeof partner !== 'undefined';
});
// Fetch partner ids
return new Model("mail.followers")
.call("read", [follower_ids, ["partner_id"]])
.then(function(res_partners) {
// Filter result and push to array
var res_partners_filtered = _.map(res_partners, function(
partner
) {
if (
partner.partner_id[0] &&
partner.partner_id[0] !== session.partner_id
) {
return partner.partner_id[0];
}
}).filter(function(partner) {
return typeof partner !== "undefined";
});
return new Model('res.partner').call(
'read', [res_partners_filtered, ['name', 'email', 'user_ids']]
).then(function (recipients) {
return new Model("res.partner")
.call("read", [
res_partners_filtered,
["name", "email", "user_ids"],
])
.then(function(recipients) {
return recipients; return recipients;
}); });
});
});
},
});
});
},
get_channels_for_internal_message: function () {
var self = this;
self.result = {};
return new Model(this.context.default_model).query(
['message_follower_ids', 'partner_id']).filter(
[['id', '=', self.context.default_res_id]]).all()
.then(function (thread) {
var follower_ids = thread[0].message_follower_ids;
self.result[self.context.default_res_id] = [];
self.customer = thread[0].partner_id;
get_channels_for_internal_message: function() {
var self = this;
self.result = {};
return new Model(this.context.default_model)
.query(["message_follower_ids", "partner_id"])
.filter([["id", "=", self.context.default_res_id]])
.all()
.then(function(thread) {
var follower_ids = thread[0].message_follower_ids;
self.result[self.context.default_res_id] = [];
self.customer = thread[0].partner_id;
// Fetch channels ids
return new Model('mail.followers').call(
'read', [follower_ids, ['channel_id']]).then(function (res_channels) {
// Filter result and push to array
var res_channels_filtered = _.map(res_channels, function (channel) {
if (channel.channel_id[0]) {
return channel.channel_id[0];
}
}).filter(function (channel) {
return typeof channel !== 'undefined';
});
// Fetch channels ids
return new Model("mail.followers")
.call("read", [follower_ids, ["channel_id"]])
.then(function(res_channels) {
// Filter result and push to array
var res_channels_filtered = _.map(res_channels, function(
channel
) {
if (channel.channel_id[0]) {
return channel.channel_id[0];
}
}).filter(function(channel) {
return typeof channel !== "undefined";
});
return new Model('mail.channel').call(
'read', [res_channels_filtered, ['name', 'id']]
).then(function (recipients) {
return new Model("mail.channel")
.call("read", [res_channels_filtered, ["name", "id"]])
.then(function(recipients) {
return recipients; return recipients;
}); });
});
});
},
});
});
},
get_internal_users_ids: function () {
var ResUser = new Model('mail.compose.message');
this.users_ids = ResUser.call('get_internal_users_ids', [[]]).then( function (users_ids) {
get_internal_users_ids: function() {
var ResUser = new Model("mail.compose.message");
this.users_ids = ResUser.call("get_internal_users_ids", [[]]).then(function(
users_ids
) {
return users_ids; return users_ids;
}); });
return this.users_ids; return this.users_ids;
},
},
get_checked_channels_ids: function () {
var self = this;
var checked_channels = [];
this.$('.o_composer_suggested_channels input:checked').each(function() {
var full_name = $(this).data('fullname').toString();
_.each(self.channels_for_internal_message, function(item) {
if (full_name === item.name) {
checked_channels.push(item.id);
}
get_checked_channels_ids: function() {
var self = this;
var checked_channels = [];
this.$(".o_composer_suggested_channels input:checked").each(function() {
var full_name = $(this)
.data("fullname")
.toString();
_.each(self.channels_for_internal_message, function(item) {
if (full_name === item.name) {
checked_channels.push(item.id);
}
});
}); });
});
return checked_channels;
},
});
MailComposer.include({
init: function (parent, dataset, options) {
this._super(parent, dataset, options);
this.events['click .oe_composer_uncheck'] = 'on_uncheck_recipients';
this.suggested_channels = [];
},
on_uncheck_recipients: function () {
this.$('.o_composer_suggested_partners input:checked').each(function() {
$(this).prop('checked', false);
});
this.$('.o_composer_suggested_channels input:checked').each(function() {
$(this).prop('checked', false);
});
},
return checked_channels;
},
});
preprocess_message: function () {
var self = this;
if (self.options.is_private) {
self.context.is_private = true;
}
return this._super();
},
MailComposer.include({
init: function(parent, dataset, options) {
this._super(parent, dataset, options);
this.events["click .oe_composer_uncheck"] = "on_uncheck_recipients";
this.suggested_channels = [];
},
on_open_full_composer: function() {
if (!this.do_check_attachment_upload()){
return false;
}
on_uncheck_recipients: function() {
this.$(".o_composer_suggested_partners input:checked").each(function() {
$(this).prop("checked", false);
});
this.$(".o_composer_suggested_channels input:checked").each(function() {
$(this).prop("checked", false);
});
},
var self = this;
var recipient_done = $.Deferred();
if (this.options.is_log) {
recipient_done.resolve([]);
} else {
var checked_suggested_partners = this.get_checked_suggested_partners();
recipient_done = this.check_suggested_partners(checked_suggested_partners);
}
recipient_done.then(function (partner_ids) {
var context = {
default_parent_id: self.id,
default_body: utils.get_text2html(self.$input.val()),
default_attachment_ids: _.pluck(self.get('attachment_ids'), 'id'),
default_partner_ids: partner_ids,
default_is_log: self.options.is_log,
mail_post_autofollow: true,
};
preprocess_message: function() {
var self = this;
if (self.options.is_private) {
self.context.is_private = true;
}
return this._super();
},
if (self.options && self.options.is_private) {
context.default_is_private = self.options.is_private;
on_open_full_composer: function() {
if (!this.do_check_attachment_upload()) {
return false;
} }
if (self.context.default_model && self.context.default_res_id) {
context.default_model = self.context.default_model;
context.default_res_id = self.context.default_res_id;
var self = this;
var recipient_done = $.Deferred();
if (this.options.is_log) {
recipient_done.resolve([]);
} else {
var checked_suggested_partners = this.get_checked_suggested_partners();
recipient_done = this.check_suggested_partners(
checked_suggested_partners
);
} }
recipient_done.then(function(partner_ids) {
var context = {
default_parent_id: self.id,
default_body: utils.get_text2html(self.$input.val()),
default_attachment_ids: _.pluck(self.get("attachment_ids"), "id"),
default_partner_ids: partner_ids,
default_is_log: self.options.is_log,
mail_post_autofollow: true,
};
self.do_action({
type: 'ir.actions.act_window',
res_model: 'mail.compose.message',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: context,
}, {
on_close: function() {
self.trigger('need_refresh');
var parent = self.getParent();
chat_manager.get_messages({model: parent.model, res_id: parent.res_id});
},
}).then(self.trigger.bind(self, 'close_composer'));
});
},
if (self.options && self.options.is_private) {
context.default_is_private = self.options.is_private;
}
get_checked_suggested_partners: function () {
var checked_partners = this._super(this, arguments);
// workaround: odoo code works only when all partners are checked intially,
// while may select only some of them (internal recepients)
_.each(checked_partners, function (partner) {
partner.checked = true;
});
return checked_partners;
},
if (self.context.default_model && self.context.default_res_id) {
context.default_model = self.context.default_model;
context.default_res_id = self.context.default_res_id;
}
});
self.do_action(
{
type: "ir.actions.act_window",
res_model: "mail.compose.message",
view_mode: "form",
view_type: "form",
views: [[false, "form"]],
target: "new",
context: context,
},
{
on_close: function() {
self.trigger("need_refresh");
var parent = self.getParent();
chat_manager.get_messages({
model: parent.model,
res_id: parent.res_id,
});
},
}
).then(self.trigger.bind(self, "close_composer"));
});
},
get_checked_suggested_partners: function() {
var checked_partners = this._super(this, arguments);
// Workaround: odoo code works only when all partners are checked intially,
// while may select only some of them (internal recepients)
_.each(checked_partners, function(partner) {
partner.checked = true;
});
return checked_partners;
},
});
}); });

43
mail_private/static/src/js/test_private.js

@ -1,48 +1,55 @@
/* Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> /* Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).*/ License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).*/
odoo.define('mail_private.tour', function (require) {
odoo.define("mail_private.tour", function(require) {
"use strict"; "use strict";
var tour = require("web_tour.tour"); var tour = require("web_tour.tour");
var core = require('web.core');
var core = require("web.core");
var _t = core._t; var _t = core._t;
var email = 'mail_private test email';
var steps = [{
var email = "mail_private test email";
var steps = [
{
trigger: '.o_thread_message strong.o_mail_redirect:contains("Agrolait")', trigger: '.o_thread_message strong.o_mail_redirect:contains("Agrolait")',
content: _t("Open Partners Form"), content: _t("Open Partners Form"),
position: 'bottom',
}, {
position: "bottom",
},
{
trigger: "button.oe_compose_post_private", trigger: "button.oe_compose_post_private",
content: _t("Click on Private mail creating button"), content: _t("Click on Private mail creating button"),
position: "bottom"
}, {
// for some reason (due to tricky renderings) button.oe_composer_uncheck could not be find by the tour manager
position: "bottom",
},
{
// For some reason (due to tricky renderings) button.oe_composer_uncheck could not be find by the tour manager
trigger: ".o_control_panel.o_breadcrumb_full li.active", trigger: ".o_control_panel.o_breadcrumb_full li.active",
content: _t("Dummy action"), content: _t("Dummy action"),
}, {
},
{
trigger: "button.oe_composer_uncheck", trigger: "button.oe_composer_uncheck",
extra_trigger: "button.oe_composer_uncheck", extra_trigger: "button.oe_composer_uncheck",
content: _t("Uncheck all Followers"), content: _t("Uncheck all Followers"),
timeout: 22000, timeout: 22000,
}, {
},
{
trigger: "div.o_composer_suggested_partners input:first", trigger: "div.o_composer_suggested_partners input:first",
content: _t("Check the first one"), content: _t("Check the first one"),
}, {
},
{
trigger: "textarea.o_composer_text_field:first", trigger: "textarea.o_composer_text_field:first",
content: _t("Write some email"), content: _t("Write some email"),
run: function() { run: function() {
$('textarea.o_composer_text_field:first').val(email);
$("textarea.o_composer_text_field:first").val(email);
}, },
}, {
},
{
trigger: ".o_composer_buttons .o_composer_button_send", trigger: ".o_composer_buttons .o_composer_button_send",
content: _t("Send email"), content: _t("Send email"),
}, {
},
{
trigger: ".o_mail_thread .o_thread_message:contains(" + email + ")", trigger: ".o_mail_thread .o_thread_message:contains(" + email + ")",
content: _t("Send email"), content: _t("Send email"),
}
},
]; ];
tour.register('mail_private_tour', { test: true, url: '/web' }, steps);
tour.register("mail_private_tour", {test: true, url: "/web"}, steps);
}); });

23
mail_private/static/src/xml/mail_private.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<!--Copyright 2016 x620 <https://github.com/x620> <!--Copyright 2016 x620 <https://github.com/x620>
Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev> Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2016 manawi <https://github.com/manawi> Copyright 2016 manawi <https://github.com/manawi>
@ -9,7 +9,11 @@
<t t-extend="mail.Chatter"> <t t-extend="mail.Chatter">
<t t-jquery="button[title='Send a message']" t-operation="after"> <t t-jquery="button[title='Send a message']" t-operation="after">
<button class="btn btn-sm btn-link oe_compose_post_private" t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox" title="Send a message to specified recipients only">Send internal message</button>
<button
class="btn btn-sm btn-link oe_compose_post_private"
t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox"
title="Send a message to specified recipients only"
>Send internal message</button>
</t> </t>
</t> </t>
@ -18,7 +22,7 @@
<span class="o_chatter_composer_info" t-if="!widget.options.is_private"> <span class="o_chatter_composer_info" t-if="!widget.options.is_private">
To: Followers of To: Followers of
<t t-if="widget.options.record_name"> <t t-if="widget.options.record_name">
&quot;<t t-esc="widget.options.record_name"/>&quot;
&quot;<t t-esc="widget.options.record_name" />&quot;
</t> </t>
<t t-if="!widget.options.record_name"> <t t-if="!widget.options.record_name">
this document this document
@ -29,14 +33,19 @@
<div class="o_composer_suggested_channels"> <div class="o_composer_suggested_channels">
<t t-foreach='widget.suggested_channels' t-as='channel'> <t t-foreach='widget.suggested_channels' t-as='channel'>
<div t-attf-title="Add as channel and follower"> <div t-attf-title="Add as channel and follower">
<input type="checkbox"
<input
type="checkbox"
t-att-checked="channel.checked ? 'checked' : undefined" t-att-checked="channel.checked ? 'checked' : undefined"
t-att-data-fullname="channel.full_name"/>
<t t-esc="channel.name"/>
t-att-data-fullname="channel.full_name"
/>
<t t-esc="channel.name" />
</div> </div>
</t> </t>
</div> </div>
<button class="btn btn-sm btn-link oe_composer_uncheck" t-if="widget.options.is_private">Uncheck all</button>
<button
class="btn btn-sm btn-link oe_composer_uncheck"
t-if="widget.options.is_private"
>Uncheck all</button>
</t> </t>
</t> </t>

19
mail_private/template.xml

@ -1,19 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--Copyright 2016 x620 <https://github.com/x620> <!--Copyright 2016 x620 <https://github.com/x620>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).--> License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<openerp> <openerp>
<data> <data>
<template <template
id="assets_backend"
name="mail_private_assets_backend"
inherit_id="web.assets_backend">
id="assets_backend"
name="mail_private_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script <script
type="text/javascript"
src="/mail_private/static/src/js/mail_private.js"></script>
type="text/javascript"
src="/mail_private/static/src/js/mail_private.js"
/>
<script <script
type="text/javascript"
src="/mail_private/static/src/js/test_private.js"></script>
type="text/javascript"
src="/mail_private/static/src/js/test_private.js"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

16
mail_private/tests/test_js.py

@ -9,7 +9,6 @@ from odoo.api import Environment
@odoo.tests.common.at_install(True) @odoo.tests.common.at_install(True)
@odoo.tests.common.post_install(True) @odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase): class TestUi(odoo.tests.HttpCase):
def test_01_mail_private(self): def test_01_mail_private(self):
# needed because tests are run before the module is marked as # needed because tests are run before the module is marked as
# installed. In js web will only load qweb coming from modules # installed. In js web will only load qweb coming from modules
@ -17,10 +16,15 @@ class TestUi(odoo.tests.HttpCase):
# this you end up with js, css but no qweb. # this you end up with js, css but no qweb.
cr = self.registry.cursor() cr = self.registry.cursor()
env = Environment(cr, self.uid, {}) env = Environment(cr, self.uid, {})
env['ir.module.module'].search([('name', '=', 'mail_private')], limit=1).state = 'installed'
env["ir.module.module"].search(
[("name", "=", "mail_private")], limit=1
).state = "installed"
cr.release() cr.release()
self.phantom_js("/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_private_tour', 1000)",
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_private_tour.ready",
login="admin", timeout=70)
self.phantom_js(
"/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_private_tour', 1000)",
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_private_tour.ready",
login="admin",
timeout=70,
)

1
mail_recovery/__init__.py

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

22
mail_recovery/__manifest__.py

@ -1,19 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': "Mail Recovery",
'summary': """Backup and recover unsent message""",
'author': "IT-Projects LLC, Ildar Nasyrov",
'license': 'LGPL-3',
"name": "Mail Recovery",
"summary": """Backup and recover unsent message""",
"author": "IT-Projects LLC, Ildar Nasyrov",
"license": "LGPL-3",
"price": 190.00, "price": 190.00,
"currency": "EUR", "currency": "EUR",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
'website': "https://twitter.com/nasyrov_ildar",
'category': 'Discuss',
'images': ['images/mail_recovery.png'],
'version': '1.0.0',
'depends': ['mail'],
'data': [
'data.xml',
],
"website": "https://twitter.com/nasyrov_ildar",
"category": "Discuss",
"images": ["images/mail_recovery.png"],
"vesion": "10.0.1.0.0",
"depends": ["mail"],
"data": ["data.xml"],
"installable": True, "installable": True,
} }

13
mail_recovery/data.xml

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp> <openerp>
<data> <data>
<template id="assets_backend" name="message storage" inherit_id="web.assets_backend">
<template
id="assets_backend"
name="message storage"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script type="text/javascript" src="/mail_recovery/static/src/js/mail_recovery.js"></script>
<script
type="text/javascript"
src="/mail_recovery/static/src/js/mail_recovery.js"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

15
mail_recovery/static/src/js/mail_recovery.js

@ -1,11 +1,12 @@
odoo.define('mail_recovery', function (require) {
var composer = require('mail.composer');
odoo.define("mail_recovery", function(require) {
"use strict";
var composer = require("mail.composer");
composer.BasicComposer.include({ composer.BasicComposer.include({
init: function(){
init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.events['focus .o_composer_input textarea'] = 'on_focus_textarea';
this.events['keyup .o_composer_input textarea'] = 'on_keyup_textarea';
this.events["focus .o_composer_input textarea"] = "on_focus_textarea";
this.events["keyup .o_composer_input textarea"] = "on_keyup_textarea";
}, },
on_focus_textarea: function(event) { on_focus_textarea: function(event) {
var $input = $(event.target); var $input = $(event.target);
@ -16,8 +17,8 @@ odoo.define('mail_recovery', function (require) {
on_keyup_textarea: function(event) { on_keyup_textarea: function(event) {
window.localStorage.message_storage = $(event.target).val(); window.localStorage.message_storage = $(event.target).val();
}, },
send_message: function (event) {
window.localStorage.message_storage = '';
send_message: function(event) {
window.localStorage.message_storage = "";
return this._super(event); return this._super(event);
}, },
}); });

1
mail_reply/__init__.py

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

18
mail_reply/__manifest__.py

@ -3,26 +3,18 @@
"name": """Always show reply button""", "name": """Always show reply button""",
"summary": """Got a message out of a Record? Now you can reply to it too!""", "summary": """Got a message out of a Record? Now you can reply to it too!""",
"category": "Discuss", "category": "Discuss",
"images": ['images/mail_reply.jpg'],
"version": "1.0.0",
"images": ["images/mail_reply.jpg"],
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
"price": 40.00, "price": 40.00,
"currency": "EUR", "currency": "EUR",
"depends": [
"mail_base",
],
"depends": ["mail_base"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
'templates.xml'
],
"qweb": [
"static/src/xml/reply_button.xml",
],
"data": ["templates.xml"],
"qweb": ["static/src/xml/reply_button.xml"],
"demo": [], "demo": [],
"installable": True, "installable": True,
"auto_install": False, "auto_install": False,

73
mail_reply/static/src/js/mail_reply.js

@ -1,46 +1,47 @@
odoo.define('mail_reply.reply', function (require) {
"use strict";
odoo.define("mail_reply.reply", function(require) {
"use strict";
var core = require('web.core');
var base_obj = require('mail_base.base');
var core = require("web.core");
var base_obj = require("mail_base.base");
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
ChatAction.include({
select_message: function(message_id) {
this._super.apply(this, arguments);
var message = base_obj.chat_manager.get_message(message_id);
var subject = '';
if (message.record_name){
subject = "Re: " + message.record_name;
} else if (message.subject){
subject = "Re: " + message.subject;
}
this.extended_composer.set_subject(subject);
},
on_post_message: function(message){
var self = this;
var options = this.selected_message ? {} : {channel_id: this.channel.id};
if (this.selected_message) {
message.subtype = 'mail.mt_comment';
message.subtype_id = false;
message.message_type = 'comment';
message.content_subtype = 'html';
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({
select_message: function(message_id) {
this._super.apply(this, arguments);
var message = base_obj.chat_manager.get_message(message_id);
var subject = "";
if (message.record_name) {
subject = "Re: " + message.record_name;
} else if (message.subject) {
subject = "Re: " + message.subject;
}
this.extended_composer.set_subject(subject);
},
on_post_message: function(message) {
var self = this;
var options = this.selected_message ? {} : {channel_id: this.channel.id};
if (this.selected_message) {
message.subtype = "mail.mt_comment";
message.subtype_id = false;
message.message_type = "comment";
message.content_subtype = "html";
options.model = this.selected_message.model;
options.res_id = this.selected_message.res_id;
options.parent_id = this.selected_message.id;
}
base_obj.chat_manager
.post_message(message, options)
.then(function() {
options.model = this.selected_message.model;
options.res_id = this.selected_message.res_id;
options.parent_id = this.selected_message.id;
}
base_obj.chat_manager.post_message(message, options).then(function() {
if (self.selected_message) { if (self.selected_message) {
self.render_snackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000);
self.render_snackbar(
"mail.chat.MessageSentSnackbar",
{record_name: self.selected_message.record_name},
5000
);
self.unselect_message(); self.unselect_message();
} else { } else {
self.thread.scroll_to(); self.thread.scroll_to();
} }
}); });
}
});
},
});
}); });

14
mail_reply/static/src/xml/reply_button.xml

@ -1,10 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template> <template>
<t t-extend="mail.ChatThread.Message"> <t t-extend="mail.ChatThread.Message">
<t t-jquery='i[class="fa fa-reply o_thread_message_reply"]' t-operation="replace">
<i t-if="message.author_id != 'ODOOBOT' &amp;&amp; message.model != 'mail.channel' &amp;&amp; options.display_reply_icon"
<t
t-jquery='i[class="fa fa-reply o_thread_message_reply"]'
t-operation="replace"
>
<i
t-if="message.author_id != 'ODOOBOT' &amp;&amp; message.model != 'mail.channel' &amp;&amp; options.display_reply_icon"
class="fa fa-reply o_thread_message_reply" class="fa fa-reply o_thread_message_reply"
t-att-data-message-id="message.id" title="Reply"/>
t-att-data-message-id="message.id"
title="Reply"
/>
</t> </t>
</t> </t>
</template> </template>

15
mail_reply/templates.xml

@ -1,11 +1,16 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data> <data>
<template id="mail_reply_assets_backend"
name="mail_reply_assets_backend"
inherit_id="web.assets_backend">
<template
id="mail_reply_assets_backend"
name="mail_reply_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script src="/mail_reply/static/src/js/mail_reply.js" type="text/javascript"></script>
<script
src="/mail_reply/static/src/js/mail_reply.js"
type="text/javascript"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

25
mail_sent/__manifest__.py

@ -3,27 +3,16 @@
"name": "Sentbox", "name": "Sentbox",
"summary": """Quick way to find sent messages""", "summary": """Quick way to find sent messages""",
"category": "Discuss", "category": "Discuss",
"images": ['images/menu.png'],
"images": ["images/menu.png"],
"version": "10.0.1.1.0", "version": "10.0.1.1.0",
"author": "IT-Projects LLC, Ivan Yelizariev, Pavel Romanchenko", "author": "IT-Projects LLC, Ivan Yelizariev, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
'price': 40.00,
'currency': 'EUR',
"depends": [
"base",
"mail",
"mail_base"
],
"data": [
"views/templates.xml",
],
"qweb": [
"static/src/xml/menu.xml",
],
'installable': True,
"price": 40.00,
"currency": "EUR",
"depends": ["base", "mail", "mail_base"],
"data": ["views/templates.xml"],
"qweb": ["static/src/xml/menu.xml"],
"installable": True,
} }

30
mail_sent/models.py

@ -1,36 +1,40 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from openerp import api, models, fields
from openerp import api, fields, models
class MailMessage(models.Model): class MailMessage(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
sent = fields.Boolean('Sent', compute="_compute_sent", help='Was message sent to someone', store=True)
sent = fields.Boolean(
"Sent", compute="_compute_sent", help="Was message sent to someone", store=True
)
@api.depends('author_id', 'partner_ids')
@api.depends("author_id", "partner_ids")
def _compute_sent(self): def _compute_sent(self):
for r in self: for r in self:
r_sudo = r.sudo() r_sudo = r.sudo()
sent = len(r_sudo.partner_ids) > 1 \
or len(r_sudo.partner_ids) == 1 \
and r_sudo.author_id \
and r_sudo.partner_ids[0].id != r_sudo.author_id.id \
or r_sudo.model == 'mail.channel' \
sent = (
len(r_sudo.partner_ids) > 1
or len(r_sudo.partner_ids) == 1
and r_sudo.author_id
and r_sudo.partner_ids[0].id != r_sudo.author_id.id
or r_sudo.model == "mail.channel"
and r_sudo.res_id and r_sudo.res_id
)
r.sent = sent r.sent = sent
@api.multi @api.multi
def message_format(self): def message_format(self):
message_values = super(MailMessage, self).message_format() message_values = super(MailMessage, self).message_format()
message_index = {message['id']: message for message in message_values}
message_index = {message["id"]: message for message in message_values}
for item in self: for item in self:
msg = message_index.get(item.id) msg = message_index.get(item.id)
if msg: if msg:
msg['sent'] = item.sent
msg["sent"] = item.sent
return message_values return message_values
class MailComposeMessage(models.TransientModel): class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
sent = fields.Boolean('Sent', help='dummy field to fix inherit error')
_inherit = "mail.compose.message"
sent = fields.Boolean("Sent", help="dummy field to fix inherit error")

113
mail_sent/static/src/js/sent.js

@ -1,79 +1,74 @@
odoo.define('mail_sent.sent', function (require) {
"use strict";
odoo.define("mail_sent.sent", function(require) {
"use strict";
var base_obj = require('mail_base.base');
var base_obj = require("mail_base.base");
//-------------------------------------------------------------------------------
var bus = require('bus.bus').bus;
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
var Model = require('web.Model');
var session = require('web.session');
var time = require('web.time');
var web_client = require('web.web_client');
// -------------------------------------------------------------------------------
var core = require("web.core");
var session = require("web.session");
var _lt = core._lt;
//-------------------------------------------------------------------------------
var _lt = core._lt;
// -------------------------------------------------------------------------------
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
ChatAction.include({
init: function(parent, action, options) {
this._super.apply(this, arguments);
var channel_name = 'channel_sent';
// Add channel Sent for show "Send message" button
this.channels_show_send_button.push(channel_name);
// Add channel Sent for enable "display_subject" option
this.channels_display_subject.push(channel_name);
},
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({
init: function(parent, action, options) {
this._super.apply(this, arguments);
var channel_name = "channel_sent";
// Add channel Sent for show "Send message" button
this.channels_show_send_button.push(channel_name);
// Add channel Sent for enable "display_subject" option
this.channels_display_subject.push(channel_name);
},
update_message_on_current_channel: function(current_channel_id, message){
var result = this._super.apply(this, arguments);
var sent = current_channel_id === "channel_sent" && !message.is_sent;
return sent || result;
}
});
update_message_on_current_channel: function(current_channel_id, message) {
var result = this._super.apply(this, arguments);
var sent = current_channel_id === "channel_sent" && !message.is_sent;
return sent || result;
},
});
// Inherit class and override methods
base_obj.MailTools.include({
get_properties: function(msg){
var properties = this._super.apply(this, arguments);
properties.is_sent = this.property_descr("channel_sent", msg, this);
return properties;
},
// Inherit class and override methods
base_obj.MailTools.include({
get_properties: function(msg) {
var properties = this._super.apply(this, arguments);
properties.is_sent = this.property_descr("channel_sent", msg, this);
return properties;
},
set_channel_flags: function(data, msg){
this._super.apply(this, arguments);
if (data.sent && data.author_id[0] == session.partner_id) {
msg.is_sent = true;
}
return msg;
},
set_channel_flags: function(data, msg) {
this._super.apply(this, arguments);
if (data.sent && data.author_id[0] === session.partner_id) {
msg.is_sent = true;
}
return msg;
},
get_channel_array: function(msg){
var arr = this._super.apply(this, arguments);
return arr.concat('channel_sent');
},
get_channel_array: function(msg) {
var arr = this._super.apply(this, arguments);
return arr.concat("channel_sent");
},
get_domain: function(channel){
return (channel.id === "channel_sent") ? [
['sent', '=', true],
['author_id.user_ids', 'in', [openerp.session.uid]]
] : this._super.apply(this, arguments);
}
});
get_domain: function(channel) {
return channel.id === "channel_sent"
? [
["sent", "=", true],
["author_id.user_ids", "in", [openerp.session.uid]],
]
: this._super.apply(this, arguments);
},
});
base_obj.chat_manager.is_ready.then(function(){
base_obj.chat_manager.is_ready.then(function() {
// Add sent channel // Add sent channel
base_obj.chat_manager.mail_tools.add_channel({ base_obj.chat_manager.mail_tools.add_channel({
id: "channel_sent", id: "channel_sent",
name: _lt("Sent"), name: _lt("Sent"),
type: "static"
type: "static",
}); });
return $.when(); return $.when();
}); });
return base_obj.chat_manager;
return base_obj.chat_manager;
}); });

14
mail_sent/static/src/xml/menu.xml

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template> <template>
<!--Inherit Sidebar and add Sent menu item after Starred --> <!--Inherit Sidebar and add Sent menu item after Starred -->
<t t-extend="mail.chat.Sidebar"> <t t-extend="mail.chat.Sidebar">
<t t-jquery="div[data-channel-id=channel_inbox]" t-operation="after"> <t t-jquery="div[data-channel-id=channel_inbox]" t-operation="after">
<div t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_sent') ? 'o_active': ''}" data-channel-id="channel_sent">
<span class="o_channel_name mail_sent"> <i class="fa fa-send-o"/> Sent </span>
<div
t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_sent') ? 'o_active': ''}"
data-channel-id="channel_sent"
>
<span class="o_channel_name mail_sent"> <i
class="fa fa-send-o"
/> Sent </span>
</div> </div>
</t> </t>
</t> </t>
@ -13,7 +18,8 @@
<t t-jquery="t:last-child" t-operation="after"> <t t-jquery="t:last-child" t-operation="after">
<t t-if="options.channel_id==='channel_sent'"> <t t-if="options.channel_id==='channel_sent'">
<div class="o_thread_title">No sent messages</div> <div class="o_thread_title">No sent messages</div>
<div>You can send messages and then these messages will appear here.</div>
<div
>You can send messages and then these messages will appear here.</div>
</t> </t>
</t> </t>
</t> </t>

10
mail_sent/tests/test_js.py

@ -5,7 +5,6 @@ import odoo.tests
@odoo.tests.common.at_install(False) @odoo.tests.common.at_install(False)
@odoo.tests.common.post_install(True) @odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase): class TestUi(odoo.tests.HttpCase):
def test_01_mail_sent(self): def test_01_mail_sent(self):
# wait till page loaded and then click and wait again # wait till page loaded and then click and wait again
code = """ code = """
@ -14,5 +13,10 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000); setTimeout(function () {console.log('ok');}, 3000);
}, 1000); }, 1000);
""" """
link = '/web#action=%s' % self.ref('mail.mail_channel_action_client_chat')
self.phantom_js(link, code, "odoo.__DEBUG__.services['mail_sent.sent'].is_ready", login="demo")
link = "/web#action=%s" % self.ref("mail.mail_channel_action_client_chat")
self.phantom_js(
link,
code,
"odoo.__DEBUG__.services['mail_sent.sent'].is_ready",
login="demo",
)

12
mail_sent/views/templates.xml

@ -1,11 +1,13 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data> <data>
<template id="mail_sent_assets_backend"
name="mail_sent_assets_backend"
inherit_id="web.assets_backend">
<template
id="mail_sent_assets_backend"
name="mail_sent_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script src="/mail_sent/static/src/js/sent.js" type="text/javascript"></script>
<script src="/mail_sent/static/src/js/sent.js" type="text/javascript" />
</xpath> </xpath>
</template> </template>
</data> </data>

16
mail_to/__manifest__.py

@ -8,26 +8,18 @@
"name": """Show message recipients""", "name": """Show message recipients""",
"summary": """Allows you be sure, that all discussion participants were notified""", "summary": """Allows you be sure, that all discussion participants were notified""",
"category": "Discuss", "category": "Discuss",
"images": ['images/1.png'],
"images": ["images/1.png"],
"version": "10.0.1.1.0", "version": "10.0.1.1.0",
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
"price": 40.00, "price": 40.00,
"currency": "EUR", "currency": "EUR",
"depends": [
'mail_base',
],
"depends": ["mail_base"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
'templates.xml',
],
"qweb": [
'static/src/xml/recipient.xml',
],
"data": ["templates.xml"],
"qweb": ["static/src/xml/recipient.xml"],
"demo": [], "demo": [],
"installable": True, "installable": True,
"auto_install": False, "auto_install": False,

13
mail_to/models/mail_message.py

@ -1,18 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/> # Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
# License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).
from odoo import models, api
from odoo import api, models
class MailMessage(models.Model): class MailMessage(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
@api.multi @api.multi
def message_format(self): def message_format(self):
messages_values = super(MailMessage, self).message_format() messages_values = super(MailMessage, self).message_format()
for i in messages_values: for i in messages_values:
if i['channel_ids']:
i['channel_names'] = self.env['mail.channel'].browse(i['channel_ids']).mapped(
lambda r: [r.id, '#' + r.display_name])
if i["channel_ids"]:
i["channel_names"] = (
self.env["mail.channel"]
.browse(i["channel_ids"])
.mapped(lambda r: [r.id, "#" + r.display_name])
)
return messages_values return messages_values

28
mail_to/static/src/js/mail_to.js

@ -3,13 +3,13 @@
* Copyright 2017 Artyom Losev <https://it-projects.info/> * Copyright 2017 Artyom Losev <https://it-projects.info/>
* Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/> * Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
odoo.define('mail_to.MailTo', function (require) {
odoo.define("mail_to.MailTo", function(require) {
"use strict"; "use strict";
var base_obj = require('mail_base.base');
var base_obj = require("mail_base.base");
base_obj.MailTools.include({ base_obj.MailTools.include({
make_message: function(data){
make_message: function(data) {
var msg = this._super(data); var msg = this._super(data);
msg.partner_ids = data.partner_ids; msg.partner_ids = data.partner_ids;
msg.channel_names = data.channel_names; msg.channel_names = data.channel_names;
@ -18,22 +18,22 @@ odoo.define('mail_to.MailTo', function (require) {
return msg; return msg;
} }
var more_recipients = '';
// value which define more recipients
var more_recipients = "";
// Value which define more recipients
msg.more_recipients_value = 4; msg.more_recipients_value = 4;
for (var i = 0; i < msg.recipients.length; i++){
if (i >= msg.more_recipients_value){
// append names
more_recipients += msg.recipients[i][1];
// separate them with semicolon
if (i < msg.recipients.length - 1){
more_recipients += '; ';
}
for (var i = 0; i < msg.recipients.length; i++) {
if (i >= msg.more_recipients_value) {
// Append names
more_recipients += msg.recipients[i][1];
// Separate them with semicolon
if (i < msg.recipients.length - 1) {
more_recipients += "; ";
} }
}
} }
msg.more_recipients = more_recipients; msg.more_recipients = more_recipients;
return msg; return msg;
}
},
}); });
}); });

18
mail_to/static/src/js/test_mail_to.js

@ -1,20 +1,20 @@
/* Copyright 2018 Artem Rafailov <https://it-projects.info/team/KolushovAlexandr> /* Copyright 2018 Artem Rafailov <https://it-projects.info/team/KolushovAlexandr>
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).*/ License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).*/
odoo.define('mail_to.tour', function (require) {
odoo.define("mail_to.tour", function(require) {
"use strict"; "use strict";
var tour = require("web_tour.tour"); var tour = require("web_tour.tour");
var core = require('web.core');
var core = require("web.core");
var _t = core._t; var _t = core._t;
var email = 'mail_private test email';
var steps = [{
trigger: 'a.recipient_link:first',
var steps = [
{
trigger: "a.recipient_link:first",
content: _t("Open Partners Form From Recipient Link"), content: _t("Open Partners Form From Recipient Link"),
position: 'bottom',
position: "bottom",
timeout: 70000, timeout: 70000,
}];
tour.register('mail_to_tour', { test: true, url: '/web' }, steps);
},
];
tour.register("mail_to_tour", {test: true, url: "/web"}, steps);
}); });

31
mail_to/static/src/xml/recipient.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<!--Copyright 2016 x620 <https://github.com/x620> <!--Copyright 2016 x620 <https://github.com/x620>
Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev> Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/> Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
@ -11,17 +11,32 @@
<t t-else="message.channel_ids.length > 0">To: </t> <t t-else="message.channel_ids.length > 0">To: </t>
<t t-foreach="message.partner_ids.length" t-as="i"> <t t-foreach="message.partner_ids.length" t-as="i">
<t t-if="i &lt; message.more_recipients_value"> <t t-if="i &lt; message.more_recipients_value">
<a t-att-href="_.str.sprintf('/web?#id=%s&amp;view_type=form&amp;model=res.partner', message.partner_ids[i][0])" class="recipient_link">
<i t-esc="message.partner_ids[i][1]"/><t t-if="i &lt; message.partner_ids.length - 1">; </t><t t-else="message.channel_ids.length > 0 and message.partner_ids.length &lt; 4">; </t>
<a
t-att-href="_.str.sprintf('/web?#id=%s&amp;view_type=form&amp;model=res.partner', message.partner_ids[i][0])"
class="recipient_link"
>
<i t-esc="message.partner_ids[i][1]" /><t
t-if="i &lt; message.partner_ids.length - 1"
>; </t><t
t-else="message.channel_ids.length > 0 and message.partner_ids.length &lt; 4"
>; </t>
</a> </a>
</t> </t>
</t> </t>
<t t-if="message.channel_names"> <t t-if="message.channel_names">
<t t-foreach="message.channel_ids.length" t-as="i"> <t t-foreach="message.channel_ids.length" t-as="i">
<t t-if="message.partner_ids.length &lt; message.more_recipients_value and (message.partner_ids.length + i) &lt; message.more_recipients_value">
<a t-if="message.channel_names[i]" t-att-href="_.str.sprintf('/web?#id=%s&amp;view_type=form&amp;model=mail.channel', message.channel_names[i][0])" class="recipient_link">
<i t-esc="message.channel_names[i][1]"/><t t-if="i &lt; message.channel_ids.length - 1">; </t>
<t
t-if="message.partner_ids.length &lt; message.more_recipients_value and (message.partner_ids.length + i) &lt; message.more_recipients_value"
>
<a
t-if="message.channel_names[i]"
t-att-href="_.str.sprintf('/web?#id=%s&amp;view_type=form&amp;model=mail.channel', message.channel_names[i][0])"
class="recipient_link"
>
<i t-esc="message.channel_names[i][1]" /><t
t-if="i &lt; message.channel_ids.length - 1"
>; </t>
</a> </a>
</t> </t>
</t> </t>
@ -30,7 +45,9 @@
<t t-if="message.recipients.length &gt; message.more_recipients_value"> <t t-if="message.recipients.length &gt; message.more_recipients_value">
<span t-att-title="message.more_recipients"> <span t-att-title="message.more_recipients">
and <t t-esc="message.recipients.length - message.more_recipients_value"/> more
and <t
t-esc="message.recipients.length - message.more_recipients_value"
/> more
</span> </span>
</t> </t>
</t></span> </t></span>

22
mail_to/templates.xml

@ -1,17 +1,25 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<!--Copyright 2016 x620 <https://github.com/x620> <!--Copyright 2016 x620 <https://github.com/x620>
Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev> Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/> Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).--> License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<openerp> <openerp>
<data> <data>
<template id="mail_to_assets_backend"
name="mail_to_assets_backend"
inherit_id="web.assets_backend">
<template
id="mail_to_assets_backend"
name="mail_to_assets_backend"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" href="/mail_to/static/src/css/mail_to.css"/>
<script src="/mail_to/static/src/js/mail_to.js" type="text/javascript"></script>
<script src="/mail_to/static/src/js/test_mail_to.js" type="text/javascript"></script>
<link rel="stylesheet" href="/mail_to/static/src/css/mail_to.css" />
<script
src="/mail_to/static/src/js/mail_to.js"
type="text/javascript"
/>
<script
src="/mail_to/static/src/js/test_mail_to.js"
type="text/javascript"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

16
mail_to/tests/test_default.py

@ -8,14 +8,18 @@ from odoo.api import Environment
@odoo.tests.common.at_install(True) @odoo.tests.common.at_install(True)
@odoo.tests.common.post_install(True) @odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase): class TestUi(odoo.tests.HttpCase):
def test_01_mail_to(self): def test_01_mail_to(self):
cr = self.registry.cursor() cr = self.registry.cursor()
env = Environment(cr, self.uid, {}) env = Environment(cr, self.uid, {})
env['ir.module.module'].search([('name', '=', 'mail_to')], limit=1).state = 'installed'
env["ir.module.module"].search(
[("name", "=", "mail_to")], limit=1
).state = "installed"
cr.release() cr.release()
self.phantom_js("/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_to_tour', 1000)",
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_to_tour.ready",
login="admin", timeout=200)
self.phantom_js(
"/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_to_tour', 1000)",
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_to_tour.ready",
login="admin",
timeout=200,
)

18
mailgun/__manifest__.py

@ -1,14 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': "Mailgun",
'author': "IT-Projects LLC, Ildar Nasyrov",
'license': 'LGPL-3',
"name": "Mailgun",
"author": "IT-Projects LLC, Ildar Nasyrov",
"license": "LGPL-3",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
'website': "https://twitter.com/nasyrov_ildar",
'category': 'Discuss',
'version': '1.1.0',
'depends': ['mail'],
'data': [
'data/cron.xml',
],
"website": "https://twitter.com/nasyrov_ildar",
"category": "Discuss",
"vesion": "10.0.1.1.0",
"depends": ["mail"],
"data": ["data/cron.xml"],
} }

16
mailgun/controllers/main.py

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
import re
class MailMailgun(http.Controller): class MailMailgun(http.Controller):
@http.route('/mailgun/notify', auth='public', type='http', csrf=False)
@http.route("/mailgun/notify", auth="public", type="http", csrf=False)
def mailgun_notify(self, **kw): def mailgun_notify(self, **kw):
# mailgun notification in json format # mailgun notification in json format
message_url = kw.get('message-url')
if not re.match('^https://[^/]*api.mailgun.net/', message_url):
message_url = kw.get("message-url")
if not re.match("^https://[^/]*api.mailgun.net/", message_url):
# simple security check failed # simple security check failed
raise Exception('wrong message-url')
request.env['mail.thread'].sudo().mailgun_fetch_message(message_url)
return 'ok'
raise Exception("wrong message-url")
request.env["mail.thread"].sudo().mailgun_fetch_message(message_url)
return "ok"

2
mailgun/data/cron.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp> <openerp>
<data noupdate="1"> <data noupdate="1">
<record id="mailgun_domain_verification" model="ir.cron"> <record id="mailgun_domain_verification" model="ir.cron">

3
mailgun/doc/index.rst

@ -51,6 +51,3 @@ Usage
* Open ``Discuss`` in odoo * Open ``Discuss`` in odoo
* See your message there * See your message there
* Reply to the message and check it in your mail client (e.g. gmail.com) * Reply to the message and check it in your mail client (e.g. gmail.com)

35
mailgun/models.py

@ -1,36 +1,43 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging
import requests import requests
import simplejson import simplejson
from openerp import api, models
from openerp import models, api
import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class MailThread(models.AbstractModel): class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
_inherit = "mail.thread"
@api.model @api.model
def mailgun_fetch_message(self, message_url): def mailgun_fetch_message(self, message_url):
api_key = self.env['ir.config_parameter'].sudo().get_param('mailgun.apikey')
res = requests.get(message_url, headers={'Accept': 'message/rfc2822'}, auth=('api', api_key), verify=False)
self.message_process(False, res.json().get('body-mime'))
api_key = self.env["ir.config_parameter"].sudo().get_param("mailgun.apikey")
res = requests.get(
message_url,
headers={"Accept": "message/rfc2822"},
auth=("api", api_key),
verify=False,
)
self.message_process(False, res.json().get("body-mime"))
class IrConfigParameter(models.Model): class IrConfigParameter(models.Model):
_inherit = ['ir.config_parameter']
_inherit = ["ir.config_parameter"]
@api.model @api.model
def mailgun_verify(self): def mailgun_verify(self):
verified = self.get_param('mailgun.verified')
verified = self.get_param("mailgun.verified")
if verified: if verified:
return return
api_key = self.get_param('mailgun.apikey')
mail_domain = self.get_param('mail.catchall.domain')
api_key = self.get_param("mailgun.apikey")
mail_domain = self.get_param("mail.catchall.domain")
if api_key and mail_domain: if api_key and mail_domain:
url = "https://api.mailgun.net/v3/domains/%s/verify" % mail_domain url = "https://api.mailgun.net/v3/domains/%s/verify" % mail_domain
res = requests.put(url, auth=("api", api_key)) res = requests.put(url, auth=("api", api_key))
if res.status_code == 200 and simplejson.loads(res.text)["domain"]["state"] == "active":
self.set_param('mailgun.verified', '1')
if (
res.status_code == 200
and simplejson.loads(res.text)["domain"]["state"] == "active"
):
self.set_param("mailgun.verified", "1")

22
res_partner_company_messages/__manifest__.py

@ -1,18 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': "Aggregate messages from company's contacts",
'version': '1.0.0',
'author': 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
"name": "Aggregate messages from company's contacts",
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Ivan Yelizariev",
"license": "LGPL-3",
"price": 70.00, "price": 70.00,
"currency": "EUR", "currency": "EUR",
'category': 'Discuss',
"category": "Discuss",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
'website': 'https://twitter.com/yelizariev',
'images': ['images/child.png', 'images/parent.png'],
'depends': ['mail'],
'data': [
'views.xml',
],
'installable': True
"website": "https://twitter.com/yelizariev",
"images": ["images/child.png", "images/parent.png"],
"depends": ["mail"],
"data": ["views.xml"],
"installable": True,
} }

18
res_partner_company_messages/models.py

@ -1,19 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from openerp import api
from openerp import models
from openerp import api, models
class Partner(models.Model): class Partner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
@api.multi @api.multi
def read(self, fields=None, load='_classic_read'):
def read(self, fields=None, load="_classic_read"):
res = super(Partner, self).read(fields=fields, load=load) res = super(Partner, self).read(fields=fields, load=load)
if fields and 'message_ids' in fields:
if fields and "message_ids" in fields:
for vals in res: for vals in res:
partner = self.browse(vals['id'])
partner = self.browse(vals["id"])
if not partner.is_company: if not partner.is_company:
continue continue
domain = [('model', '=', 'res.partner'), ('res_id', 'in', [partner.id] + partner.child_ids.ids)]
vals['message_ids'] = self.env['mail.message'].search(domain).ids
domain = [
("model", "=", "res.partner"),
("res_id", "in", [partner.id] + partner.child_ids.ids),
]
vals["message_ids"] = self.env["mail.message"].search(domain).ids
return res return res

2
res_partner_company_messages/views.xml

@ -1,3 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp><data> <openerp><data>
</data></openerp> </data></openerp>

16
res_partner_mails_count/__manifest__.py

@ -3,27 +3,21 @@
"name": """Partner mails count""", "name": """Partner mails count""",
"summary": """Displays amount of incoming and outgoing partner mails.""", "summary": """Displays amount of incoming and outgoing partner mails.""",
"category": "Discuss", "category": "Discuss",
"images": ['images/1.png'],
"version": "1.0.0",
"images": ["images/1.png"],
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko", "author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info", "website": "https://it-projects.info",
"license": "LGPL-3", "license": "LGPL-3",
"price": 30.00, "price": 30.00,
"currency": "EUR", "currency": "EUR",
"depends": [ "depends": [
'mail_all',
"mail_all",
# 'web_tour_extra', # 'web_tour_extra',
], ],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
'views/res_partner_mails_count.xml',
'templates.xml',
],
"demo": [
],
"data": ["views/res_partner_mails_count.xml", "templates.xml"],
"demo": [],
"installable": True, "installable": True,
"auto_install": False, "auto_install": False,
} }

14
res_partner_mails_count/models.py

@ -1,19 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from openerp import models, fields, api
from openerp import api, fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
mails_to = fields.Integer(compute="_compute_mails_to") mails_to = fields.Integer(compute="_compute_mails_to")
mails_from = fields.Integer(compute="_compute_mails_from") mails_from = fields.Integer(compute="_compute_mails_from")
@api.multi @api.multi
def _compute_mails_to(self): def _compute_mails_to(self):
for r in self: for r in self:
r.mails_to = self.env['mail.message'].sudo().search_count([('partner_ids', 'in', r.id)])
r.mails_to = (
self.env["mail.message"]
.sudo()
.search_count([("partner_ids", "in", r.id)])
)
@api.multi @api.multi
def _compute_mails_from(self): def _compute_mails_from(self):
for r in self: for r in self:
r.mails_from = self.env['mail.message'].sudo().search_count([('author_id', '=', r.id)])
r.mails_from = (
self.env["mail.message"].sudo().search_count([("author_id", "=", r.id)])
)

44
res_partner_mails_count/static/src/js/res_partner_mails_count_tour.js

@ -1,36 +1,38 @@
odoo.define('res_partner_mails_count.res_partner_mails_count_tour', function (require) {
'use strict';
var Core = require('web.core');
var Tour = require('web.Tour');
odoo.define("res_partner_mails_count.res_partner_mails_count_tour", function(require) {
"use strict";
var Core = require("web.core");
var Tour = require("web.Tour");
var _t = Core._t; var _t = Core._t;
Tour.register({ Tour.register({
id: 'mails_count_tour',
id: "mails_count_tour",
name: _t("Mails count Tour"), name: _t("Mails count Tour"),
mode: 'test',
path: '/web?res_partner_mails_count=tutorial#id=3&view_type=form&model=res.partner',
mode: "test",
path:
"/web?res_partner_mails_count=tutorial#id=3&view_type=form&model=res.partner",
steps: [ steps: [
{ {
title: _t("Mails count tutorial"),
content: _t("Let's see how mails count work."),
popover: { next: _t("Start Tutorial"), end: _t("Skip") },
title: _t("Mails count tutorial"),
content: _t("Let's see how mails count work."),
popover: {next: _t("Start Tutorial"), end: _t("Skip")},
}, },
{ {
title: _t("New fields"),
content: _t("Here is new fields with mails counters. Press one of it."),
element: '.mails_to',
waitFor: '.mails_to:visible',
title: _t("New fields"),
content: _t("Here is new fields with mails counters. Press one of it."),
element: ".mails_to",
waitFor: ".mails_to:visible",
}, },
{ {
title: _t("Done"), title: _t("Done"),
placement: 'top',
waitNot: '.mails_to:visible',
waitFor: '.o_mail_thread',
element: '.o_mail_thread',
content: _t("Message are found. <br/>Enjoy your day! <br/> <br/><a href='https://www.it-projects.info/apps' target='_blank'>IT-Projects LLC</a> team "),
placement: "top",
waitNot: ".mails_to:visible",
waitFor: ".o_mail_thread",
element: ".o_mail_thread",
content: _t(
"Message are found. <br/>Enjoy your day! <br/> <br/><a href='https://www.it-projects.info/apps' target='_blank'>IT-Projects LLC</a> team "
),
popover: {next: _t("Close Tutorial")}, popover: {next: _t("Close Tutorial")},
}, },
]
],
}); });
}); });

35
res_partner_mails_count/templates.xml

@ -1,4 +1,4 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp> <openerp>
<data> <data>
<!-- <!--
@ -13,34 +13,47 @@
<record id="view_res_partner_mails_count_info_form" model="ir.ui.view"> <record id="view_res_partner_mails_count_info_form" model="ir.ui.view">
<field name="name">res.partner.mails.count</field> <field name="name">res.partner.mails.count</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="priority" eval="50"/>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="priority" eval="50" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<div name="button_box" position="inside"> <div name="button_box" position="inside">
<button class="oe_stat_button mails_to" type="action"
<button
class="oe_stat_button mails_to"
type="action"
name="%(action_mails)d" name="%(action_mails)d"
context="{'search_default_partner_ids': [active_id], 'default_model': 'res.partner', 'default_res_id': active_id}" context="{'search_default_partner_ids': [active_id], 'default_model': 'res.partner', 'default_res_id': active_id}"
icon="fa-envelope">
<field string="Mails to" name="mails_to" widget="statinfo"/>
icon="fa-envelope"
>
<field string="Mails to" name="mails_to" widget="statinfo" />
</button> </button>
<button class="oe_stat_button mails_from" type="action"
<button
class="oe_stat_button mails_from"
type="action"
name="%(action_mails)d" name="%(action_mails)d"
context="{'search_default_author_id': active_id, 'default_model': 'res.partner', 'default_res_id': active_id}" context="{'search_default_author_id': active_id, 'default_model': 'res.partner', 'default_res_id': active_id}"
icon="fa-envelope-o">
<field string="Mails from" name="mails_from" widget="statinfo"/>
icon="fa-envelope-o"
>
<field
string="Mails from"
name="mails_from"
widget="statinfo"
/>
</button> </button>
</div> </div>
</field> </field>
</record> </record>
<record id="res_partner_mails_count_tutorial" model="ir.actions.act_url"> <record id="res_partner_mails_count_tutorial" model="ir.actions.act_url">
<field name="name">res_partner_mails_count Tutorial</field> <field name="name">res_partner_mails_count Tutorial</field>
<field name="url" eval="'/web?res_partner_mails_count=tutorial#id='+str(ref('base.partner_root'))+'&amp;view_type=form&amp;model=res.partner&amp;/#tutorial_extra.mails_count_tour=true'"/>
<field
name="url"
eval="'/web?res_partner_mails_count=tutorial#id='+str(ref('base.partner_root'))+'&amp;view_type=form&amp;model=res.partner&amp;/#tutorial_extra.mails_count_tour=true'"
/>
<field name="target">self</field> <field name="target">self</field>
</record> </record>
</data> </data>
<data noupdate="1"> <data noupdate="1">
<record id="base.open_menu" model="ir.actions.todo"> <record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="res_partner_mails_count_tutorial"/>
<field name="action_id" ref="res_partner_mails_count_tutorial" />
<field name="state">open</field> <field name="state">open</field>
<field name="sequence">500</field> <field name="sequence">500</field>
<field name="type">automatic</field> <field name="type">automatic</field>

1
res_partner_mails_count/tests/__init__.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import test_mail from . import test_mail
# from . import test_phantom # from . import test_phantom

64
res_partner_mails_count/tests/test_mail.py

@ -7,28 +7,58 @@ class TestMessageCount(TransactionCase):
post_install = True post_install = True
def test_count(self): def test_count(self):
new_partner1 = self.env['res.partner'].sudo().create({'name': 'rpmc Test Partner one', 'email': 'tt@tt', 'notify_email': 'always'})
new_partner2 = self.env['res.partner'].sudo().create({'name': 'rpmc Test Partner two', 'email': 'rr@rr', 'notify_email': 'always'})
self.assertEqual(new_partner1.mails_to, 0, 'rpmc: new partner have mails_to != 0')
mail_compose = self.env['mail.compose.message']
new_partner1 = (
self.env["res.partner"]
.sudo()
.create(
{
"name": "rpmc Test Partner one",
"email": "tt@tt",
"notify_email": "always",
}
)
)
new_partner2 = (
self.env["res.partner"]
.sudo()
.create(
{
"name": "rpmc Test Partner two",
"email": "rr@rr",
"notify_email": "always",
}
)
)
self.assertEqual(
new_partner1.mails_to, 0, "rpmc: new partner have mails_to != 0"
)
mail_compose = self.env["mail.compose.message"]
compose = mail_compose.with_context( compose = mail_compose.with_context(
{"default_composition_mode": "comment"}
).create(
{ {
'default_composition_mode': 'comment',
}).create(
{
'subject': 'test subj',
'body': 'test body',
'partner_ids': [(4, new_partner2.id)],
'email_from': 'tt@tt',
'author_id': new_partner1.id
})
"subject": "test subj",
"body": "test body",
"partner_ids": [(4, new_partner2.id)],
"email_from": "tt@tt",
"author_id": new_partner1.id,
}
)
compose.send_mail() compose.send_mail()
self.assertEqual(new_partner1.mails_to, 0) self.assertEqual(new_partner1.mails_to, 0)
self.assertEqual(new_partner1.mails_from, 1, 'rpmc: one message but mails_from != 1')
self.assertEqual(new_partner2.mails_to, 1, 'rpmc: one message but mails_to != 1')
self.assertEqual(
new_partner1.mails_from, 1, "rpmc: one message but mails_from != 1"
)
self.assertEqual(
new_partner2.mails_to, 1, "rpmc: one message but mails_to != 1"
)
self.assertEqual(new_partner2.mails_from, 0) self.assertEqual(new_partner2.mails_from, 0)
compose.send_mail() compose.send_mail()
self.assertEqual(new_partner1.mails_to, 0) self.assertEqual(new_partner1.mails_to, 0)
self.assertEqual(new_partner1.mails_from, 2, 'rpmc: one message but mails_from != 2')
self.assertEqual(new_partner2.mails_to, 2, 'rpmc: one message but mails_to != 2')
self.assertEqual(
new_partner1.mails_from, 2, "rpmc: one message but mails_from != 2"
)
self.assertEqual(
new_partner2.mails_to, 2, "rpmc: one message but mails_to != 2"
)
self.assertEqual(new_partner2.mails_from, 0) self.assertEqual(new_partner2.mails_from, 0)

17
res_partner_mails_count/tests/test_phantom.py

@ -5,10 +5,14 @@ import odoo.tests
@odoo.tests.common.at_install(False) @odoo.tests.common.at_install(False)
@odoo.tests.common.post_install(True) @odoo.tests.common.post_install(True)
class TestUi(odoo.tests.HttpCase): class TestUi(odoo.tests.HttpCase):
def test_01_res_partner_mails_to_count(self): def test_01_res_partner_mails_to_count(self):
# self.phantom_js('/', "openerp.Tour.run('mails_count_tour', 'test')", "openerp.Tour.tours.mails_count_tour", login="admin") # self.phantom_js('/', "openerp.Tour.run('mails_count_tour', 'test')", "openerp.Tour.tours.mails_count_tour", login="admin")
self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('mails_count_tour', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.mails_count_tour", login="admin")
self.phantom_js(
"/",
"odoo.__DEBUG__.services['web.Tour'].run('mails_count_tour', 'test')",
"odoo.__DEBUG__.services['web.Tour'].tours.mails_count_tour",
login="admin",
)
def test_02_res_partner_mails_from_count(self): def test_02_res_partner_mails_from_count(self):
# wait till page loaded and then click and wait again # wait till page loaded and then click and wait again
@ -18,5 +22,10 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000); setTimeout(function () {console.log('ok');}, 3000);
}, 3000); }, 3000);
""" """
link = '/web#id=3&view_type=form&model=res.partner'
self.phantom_js(link, code, "odoo.__DEBUG__.services['web.Tour'].tours.mails_count_tour", login="admin")
link = "/web#id=3&view_type=form&model=res.partner"
self.phantom_js(
link,
code,
"odoo.__DEBUG__.services['web.Tour'].tours.mails_count_tour",
login="admin",
)

2
res_partner_mails_count/views/res_partner_mails_count.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<openerp> <openerp>
<data> <data>
<record id="action_mails" model="ir.actions.client"> <record id="action_mails" model="ir.actions.client">

Loading…
Cancel
Save