Browse Source

Merge pull request #259 from yelizariev/11.0-pre-commit-cleanups

commit is created by 👷‍♂️ Merge Bot: https://odoo-devops.readthedocs.io/en/latest/git/github-merge-bot.html
pull/263/head
Mitchell Admin 5 years ago
committed by GitHub
parent
commit
e4acb83d23
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. 2
      mail_all/static/src/css/mail_all.css
  7. 93
      mail_all/static/src/js/mail_all.js
  8. 11
      mail_all/static/src/xml/menu.xml
  9. 1
      mail_all/tests/__init__.py
  10. 7
      mail_all/tests/test_js.py
  11. 17
      mail_all/views/templates.xml
  12. 23
      mail_archives/__manifest__.py
  13. 2
      mail_archives/static/src/css/archives.css
  14. 148
      mail_archives/static/src/js/archives.js
  15. 11
      mail_archives/static/src/xml/menu.xml
  16. 1
      mail_archives/tests/__init__.py
  17. 10
      mail_archives/tests/test_js.py
  18. 20
      mail_archives/views/templates.xml
  19. 18
      mail_base/__manifest__.py
  20. 9
      mail_base/controllers/main.py
  21. 20
      mail_base/models.py
  22. 1
      mail_base/tests/__init__.py
  23. 7
      mail_base/tests/test_default.py
  24. 12
      mail_base/views/templates.xml
  25. 26
      mail_check_immediately/__manifest__.py
  26. 51
      mail_check_immediately/models.py
  27. 53
      mail_check_immediately/static/src/js/main.js
  28. 14
      mail_check_immediately/static/src/xml/main.xml
  29. 11
      mail_check_immediately/views.xml
  30. 4
      mail_fix_553/__manifest__.py
  31. 2
      mail_fix_553/data.xml
  32. 160
      mail_fix_553/mail_fix_553.py
  33. 33
      mail_move_message/__manifest__.py
  34. 7
      mail_move_message/controllers/main.py
  35. 3
      mail_move_message/data/mail_move_message_data.xml
  36. 6
      mail_move_message/doc/index.rst
  37. 515
      mail_move_message/mail_move_message_models.py
  38. 135
      mail_move_message/mail_move_message_views.xml
  39. 5
      mail_move_message/static/src/css/mail_move_message.css
  40. 149
      mail_move_message/static/src/js/mail_move_message.js
  41. 11
      mail_move_message/static/src/xml/mail_move_message_main.xml
  42. 1
      mail_move_message/tests/__init__.py
  43. 13
      mail_move_message/tests/test_mail_move.py
  44. 18
      mail_multi_website/__init__.py
  45. 21
      mail_multi_website/__manifest__.py
  46. 2
      mail_multi_website/doc/index.rst
  47. 12
      mail_multi_website/models/ir_property.py
  48. 8
      mail_multi_website/models/mail_message.py
  49. 112
      mail_multi_website/models/mail_template.py
  50. 29
      mail_multi_website/models/mail_thread.py
  51. 16
      mail_multi_website/models/res_users.py
  52. 6
      mail_multi_website/models/website.py
  53. 53
      mail_multi_website/tests/test_fetch.py
  54. 16
      mail_multi_website/tests/test_mail_model.py
  55. 116
      mail_multi_website/tests/test_render.py
  56. 49
      mail_multi_website/tests/test_send.py
  57. 6
      mail_multi_website/views/website_views.xml
  58. 12
      mail_multi_website/wizard/mail_compose_message.py
  59. 21
      mail_private/__manifest__.py
  60. 16
      mail_private/full_composer_wizard.xml
  61. 130
      mail_private/models.py
  62. 9
      mail_private/static/src/css/mail_private.css
  63. 498
      mail_private/static/src/js/mail_private.js
  64. 43
      mail_private/static/src/js/test_private.js
  65. 27
      mail_private/static/src/xml/mail_private.xml
  66. 24
      mail_private/template.xml
  67. 20
      mail_private/tests/test_js.py
  68. 22
      mail_recovery/__manifest__.py
  69. 13
      mail_recovery/data.xml
  70. 15
      mail_recovery/static/src/js/mail_recovery.js
  71. 18
      mail_reply/__manifest__.py
  72. 2
      mail_reply/doc/index.rst
  73. 76
      mail_reply/static/src/js/mail_reply.js
  74. 14
      mail_reply/static/src/xml/reply_button.xml
  75. 15
      mail_reply/templates.xml
  76. 1
      mail_reply/tests/__init__.py
  77. 7
      mail_reply/tests/test_default.py
  78. 25
      mail_sent/__manifest__.py
  79. 30
      mail_sent/models.py
  80. 2
      mail_sent/static/src/css/sent.css
  81. 114
      mail_sent/static/src/js/sent.js
  82. 14
      mail_sent/static/src/xml/menu.xml
  83. 1
      mail_sent/tests/__init__.py
  84. 10
      mail_sent/tests/test_js.py
  85. 14
      mail_sent/views/templates.xml
  86. 16
      mail_to/__manifest__.py
  87. 13
      mail_to/models/mail_message.py
  88. 52
      mail_to/static/src/js/mail_to.js
  89. 18
      mail_to/static/src/js/test_mail_to.js
  90. 31
      mail_to/static/src/xml/recipient.xml
  91. 22
      mail_to/templates.xml
  92. 16
      mail_to/tests/test_default.py
  93. 23
      mailgun/__manifest__.py
  94. 16
      mailgun/controllers/main.py
  95. 6
      mailgun/data/ir_cron_data.xml
  96. 20
      mailgun/models/ir_config_parameter.py
  97. 19
      mailgun/models/mail_thread.py
  98. 22
      res_partner_company_messages/__manifest__.py
  99. 18
      res_partner_company_messages/models.py
  100. 2
      res_partner_company_messages/views.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=requests,simplejson

21
.travis.yml

@ -11,24 +11,25 @@ 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="11.0" TESTS="0" LINT_CHECK="0" UNIT_TEST="0"
- PYLINT_ODOO_JSLINTRC="/home/travis/maintainer-quality-tools/travis/cfg/.jslintrc"
- VERSION="11.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"
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,34 +3,24 @@
"summary": """Checkout all messages where you have access""", "summary": """Checkout all messages where you have access""",
"category": "Discuss", "category": "Discuss",
# "live_test_url": "", # "live_test_url": "",
"images": ['images/1.jpg'],
"images": ["images/1.jpg"],
"version": "11.0.1.0.0", "version": "11.0.1.0.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": "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": [],
"post_load": None, "post_load": None,
"pre_init_hook": None, "pre_init_hook": None,
"post_init_hook": None, "post_init_hook": None,
"uninstall_hook": None, "uninstall_hook": None,
'installable': True,
"installable": True,
"auto_install": False, "auto_install": False,
} }

2
mail_all/static/src/css/mail_all.css

@ -1,3 +1,3 @@
.o_channel_name.mail_all i { .o_channel_name.mail_all i {
margin-right: 4px; margin-right: 4px;
}
}

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

@ -1,57 +1,56 @@
odoo.define('mail_all.all', function (require) {
"use strict";
var chat_manager = require('mail_base.base').chat_manager;
var core = require('web.core');
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;
}
});
// override methods
var chat_manager_super = _.clone(chat_manager);
chat_manager.get_properties = function (msg) {
var properties = chat_manager_super.get_properties.apply(this, arguments);
properties.is_all = this.property_descr("channel_all", msg, this);
return properties;
};
chat_manager.set_channel_flags = function (data, msg) {
chat_manager_super.set_channel_flags.apply(this, arguments);
msg.is_all = data.author_id !== 'ODOOBOT';
return msg;
};
chat_manager.get_channel_array = function (msg) {
var arr = chat_manager_super.get_channel_array.apply(this, arguments);
return arr.concat('channel_all');
};
chat_manager.get_domain = function (channel) {
return (channel.id === "channel_all")
? []
: chat_manager_super.get_domain.apply(this, arguments);
};
odoo.define("mail_all.all", function(require) {
"use strict";
var chat_manager = require("mail_base.base").chat_manager;
var core = require("web.core");
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;
},
});
chat_manager.is_ready.then(function () {
// Override methods
var chat_manager_super = _.clone(chat_manager);
chat_manager.get_properties = function(msg) {
var properties = chat_manager_super.get_properties.apply(this, arguments);
properties.is_all = this.property_descr("channel_all", msg, this);
return properties;
};
chat_manager.set_channel_flags = function(data, msg) {
chat_manager_super.set_channel_flags.apply(this, arguments);
msg.is_all = data.author_id !== "ODOOBOT";
return msg;
};
chat_manager.get_channel_array = function(msg) {
var arr = chat_manager_super.get_channel_array.apply(this, arguments);
return arr.concat("channel_all");
};
chat_manager.get_domain = function(channel) {
return channel.id === "channel_all"
? []
: chat_manager_super.get_domain.apply(this, arguments);
};
chat_manager.is_ready.then(function() {
// Add all channel // Add all channel
chat_manager.add_channel({ chat_manager.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 chat_manager;
return 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_title_main 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_title_main 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>

1
mail_all/tests/__init__.py

@ -1,2 +1 @@
from . import test_js from . import test_js

7
mail_all/tests/test_js.py

@ -4,7 +4,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 = """
@ -13,5 +12,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"
)

17
mail_all/views/templates.xml

@ -1,12 +1,17 @@
<?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">
<link rel="stylesheet" href="/mail_all/static/src/css/mail_all.css"/>
<script src="/mail_all/static/src/js/mail_all.js" type="text/javascript"></script>
<link rel="stylesheet" href="/mail_all/static/src/css/mail_all.css" />
<script
src="/mail_all/static/src/js/mail_all.js"
type="text/javascript"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

23
mail_archives/__manifest__.py

@ -2,25 +2,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'],
"images": ["images/1.jpg"],
"version": "11.0.1.0.0", "version": "11.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,
} }

2
mail_archives/static/src/css/archives.css

@ -1,3 +1,3 @@
.o_channel_name.mail_archives i { .o_channel_name.mail_archives i {
margin-right: 4px; margin-right: 4px;
}
}

148
mail_archives/static/src/js/archives.js

@ -1,76 +1,80 @@
odoo.define('mail_archives.archives', function (require) {
"use strict";
var core = require('web.core');
var session = require('web.session');
var chat_manager = require('mail_base.base').chat_manager;
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);
},
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
var chat_manager_super = _.clone(chat_manager);
chat_manager.get_properties = function (msg) {
var properties = chat_manager_super.get_properties.apply(this, arguments);
properties.is_archive = this.property_descr("channel_archive", msg, this);
return properties;
};
chat_manager.set_channel_flags = function (data, msg) {
chat_manager_super.set_channel_flags.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;
}
return msg;
};
chat_manager.get_channel_array = function (msg) {
var arr = chat_manager_super.get_channel_array.apply(this, arguments);
return arr.concat('channel_archive');
};
chat_manager.get_domain = function (channel) {
return (channel.id === "channel_archive")
? ['|', ['partner_ids', 'in', [session.partner_id]],
['author_id', 'in', [session.partner_id]]]
: chat_manager_super.get_domain.apply(this, arguments);
};
chat_manager.is_ready.then(function () {
chat_manager.add_channel({
id: "channel_archive",
name: _lt("Archive"),
type: "static"
odoo.define("mail_archives.archives", function(require) {
"use strict";
var core = require("web.core");
var session = require("web.session");
var chat_manager = require("mail_base.base").chat_manager;
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);
},
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
var chat_manager_super = _.clone(chat_manager);
chat_manager.get_properties = function(msg) {
var properties = chat_manager_super.get_properties.apply(this, arguments);
properties.is_archive = this.property_descr("channel_archive", msg, this);
return properties;
};
chat_manager.set_channel_flags = function(data, msg) {
chat_manager_super.set_channel_flags.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;
}
return msg;
};
chat_manager.get_channel_array = function(msg) {
var arr = chat_manager_super.get_channel_array.apply(this, arguments);
return arr.concat("channel_archive");
};
chat_manager.get_domain = function(channel) {
return channel.id === "channel_archive"
? [
"|",
["partner_ids", "in", [session.partner_id]],
["author_id", "in", [session.partner_id]],
]
: chat_manager_super.get_domain.apply(this, arguments);
};
chat_manager.is_ready.then(function() {
chat_manager.add_channel({
id: "channel_archive",
name: _lt("Archive"),
type: "static",
});
});
return chat_manager;
return 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 o_mail_chat_title_main #{(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 o_mail_chat_title_main #{(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>

1
mail_archives/tests/__init__.py

@ -1,2 +1 @@
from . import test_js from . import test_js

10
mail_archives/tests/test_js.py

@ -4,7 +4,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 = """
@ -13,5 +12,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",
)

20
mail_archives/views/templates.xml

@ -1,12 +1,20 @@
<?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">
<link rel="stylesheet" href="/mail_archives/static/src/css/archives.css"/>
<script src="/mail_archives/static/src/js/archives.js" type="text/javascript"></script>
<link
rel="stylesheet"
href="/mail_archives/static/src/css/archives.css"
/>
<script
src="/mail_archives/static/src/js/archives.js"
type="text/javascript"
/>
</xpath> </xpath>
</template> </template>
</data> </data>

18
mail_base/__manifest__.py

@ -8,21 +8,13 @@
"category": "Discuss", "category": "Discuss",
"images": [], "images": [],
"version": "11.0.1.1.1", "version": "11.0.1.1.1",
"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,
} }

9
mail_base/controllers/main.py

@ -1,8 +1,9 @@
# 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 odoo.http import request
from odoo.addons.bus.controllers.main import BusController
class MailChatController(BusController): class MailChatController(BusController):
@ -12,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)

20
mail_base/models.py

@ -2,32 +2,34 @@
# Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2017 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 import api, models
from odoo 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

1
mail_base/tests/__init__.py

@ -1,2 +1 @@
from . import test_default from . import test_default

7
mail_base/tests/test_default.py

@ -4,7 +4,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_base(self): def test_01_mail_base(self):
# wait till page loaded # wait till page loaded
code = """ code = """
@ -12,5 +11,7 @@ class TestUi(odoo.tests.HttpCase):
console.log('ok'); console.log('ok');
}, 1000); }, 1000);
""" """
link = '/web#action=%s' % self.ref('mail.mail_channel_action_client_chat')
self.phantom_js(link, code, "odoo.__DEBUG__.services['mail_base.base']", login="admin")
link = "/web#action=%s" % self.ref("mail.mail_channel_action_client_chat")
self.phantom_js(
link, code, "odoo.__DEBUG__.services['mail_base.base']", login="admin"
)

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,19 +1,15 @@
{ {
'name': 'Check mail immediately',
'version': '1.0.1',
'author': 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
"name": "Check mail immediately",
"vesion": "11.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,
} }

51
mail_check_immediately/models.py

@ -1,15 +1,12 @@
import datetime import datetime
from openerp.tools.translate import _
from openerp import tools
from openerp import exceptions
from openerp import models, fields, api
from odoo import api, exceptions, fields, models, tools
from odoo.tools.translate import _
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
@ -19,36 +16,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]
@ -58,8 +73,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>

4
mail_fix_553/__manifest__.py

@ -2,11 +2,11 @@
"name": "Fix mail error 553", "name": "Fix mail error 553",
"version": "0.3", "version": "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 -->

160
mail_fix_553/mail_fix_553.py

@ -1,15 +1,14 @@
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.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import osv
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
from odoo import SUPERUSER_ID, tools
from odoo.osv import osv
from odoo.tools.safe_eval import safe_eval as eval
from odoo.tools.translate import _
from odoo.addons.base.ir.ir_mail_server import MailDeliveryException
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -17,7 +16,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
@ -35,50 +36,83 @@ 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(eval(mail.headers))
@ -88,7 +122,7 @@ class MailMail(osv.Model):
# 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
@ -104,58 +138,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

33
mail_move_message/__manifest__.py

@ -6,25 +6,18 @@
# 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).
{ {
'name': 'Mail Relocation',
'version': '11.0.1.0.7',
'author': 'IT-Projects LLC, Ivan Yelizariev, Pavel Romanchenko',
'license': 'LGPL-3',
'category': 'Discuss',
'images': ['images/m1.png'],
"name": "Mail Relocation",
"version": "11.0.1.0.7",
"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',
],
'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"],
"data": ["mail_move_message_views.xml", "data/mail_move_message_data.xml"],
"qweb": ["static/src/xml/mail_move_message_main.xml"],
"installable": True,
} }

7
mail_move_message/controllers/main.py

@ -4,6 +4,7 @@
# 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).
from odoo.http import request from odoo.http import request
from odoo.addons.bus.controllers.main import BusController from odoo.addons.bus.controllers.main import BusController
@ -14,7 +15,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_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)

3
mail_move_message/data/mail_move_message_data.xml

@ -1,8 +1,7 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<!--# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn> <!--# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
# Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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> <odoo>
<record id="mail_relocation_models" model="ir.config_parameter"> <record id="mail_relocation_models" model="ir.config_parameter">
<field name="key">mail_relocation_models</field> <field name="key">mail_relocation_models</field>

6
mail_move_message/doc/index.rst

@ -21,9 +21,9 @@ Move message
------------ ------------
* Open ``[[ Discuss ]] >> Inbox`` menu * Open ``[[ Discuss ]] >> Inbox`` menu
* Click on icon of two cross arrows
* Select a record you need
* Click **Move**
* Click on icon of two cross arrows
* Select a record you need
* Click **Move**
RESULT: The message has been moved to the record selected. RESULT: The message has been moved to the record selected.
Move to origin Move to origin

515
mail_move_message/mail_move_message_models.py

@ -5,31 +5,33 @@
# 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).
from odoo import api
from odoo import fields
from odoo import models
from odoo import api, exceptions, fields, models
from odoo.tools import email_split from odoo.tools import email_split
from odoo.tools.translate import _ from odoo.tools.translate import _
from odoo import exceptions
class Wizard(models.TransientModel): class Wizard(models.TransientModel):
_name = 'mail_move_message.wizard'
_name = "mail_move_message.wizard"
@api.model @api.model
def _model_selection(self): def _model_selection(self):
selection = [] selection = []
config_parameters = self.env['ir.config_parameter']
model_names = config_parameters.sudo().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.sudo().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
@api.model @api.model
@ -39,70 +41,107 @@ class Wizard(models.TransientModel):
available_models = self._model_selection() available_models = self._model_selection()
if len(available_models): if len(available_models):
record = self.env[available_models[0][0]].search([], limit=1) record = self.env[available_models[0][0]].search([], limit=1)
res['model_record'] = len(record) and (available_models[0][0] + ',' + str(record.id)) or False
res["model_record"] = (
len(record) and (available_models[0][0] + "," + str(record.id)) or False
)
if 'message_id' in res:
message = self.env['mail.message'].browse(res['message_id'])
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.sudo().get_param('mail_relocation_move_followers')
config_parameters = self.env["ir.config_parameter"]
res["move_followers"] = config_parameters.sudo().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_record = fields.Reference(selection="_model_selection", string='Record')
model = fields.Char(compute="_compute_model_res_id", string='Model')
res_id = fields.Integer(compute="_compute_model_res_id", string='Record')
can_move = fields.Boolean('Can move', compute='_compute_get_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_record = fields.Reference(selection="_model_selection", string="Record")
model = fields.Char(compute="_compute_model_res_id", string="Model")
res_id = fields.Integer(compute="_compute_model_res_id", string="Record")
can_move = fields.Boolean("Can move", compute="_compute_get_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(compute='_compute_is_read', string="Unread message",
help="Service field shows that this message were unread when moved")
message_to_read = fields.Boolean(
compute="_compute_is_read",
string="Unread message",
help="Service field shows that this message were unread when moved",
)
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.multi @api.multi
@api.depends('model_record')
@api.depends("model_record")
def _compute_model_res_id(self): def _compute_model_res_id(self):
for rec in self: for rec in self:
rec.model = rec.model_record and rec.model_record._name or False rec.model = rec.model_record and rec.model_record._name or False
rec.res_id = rec.model_record and rec.model_record.id or False rec.res_id = rec.model_record and rec.model_record.id or False
@api.depends('message_id')
@api.depends("message_id")
@api.multi @api.multi
def _compute_get_can_move(self): def _compute_get_can_move(self):
for r in self: for r in self:
@ -110,32 +149,43 @@ class Wizard(models.TransientModel):
@api.multi @api.multi
def _compute_is_read(self): def _compute_is_read(self):
messages = self.env['mail.message'].sudo().browse(self.message_id.all_child_ids.ids + [self.message_id.id])
messages = (
self.env["mail.message"]
.sudo()
.browse(self.message_id.all_child_ids.ids + [self.message_id.id])
)
self.message_to_read = True in [m.needaction for m in messages] self.message_to_read = True in [m.needaction for m in messages]
@api.multi @api.multi
def get_can_move_one(self): def get_can_move_one(self):
self.ensure_one() self.ensure_one()
# 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
self.can_move = not self.message_id.moved_by_message_id or self.message_id.moved_by_message_id.id == self.message_id.id
self.can_move = (
not self.message_id.moved_by_message_id
or self.message_id.moved_by_message_id.id == self.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
self.parent_id = self.message_id.moved_from_parent_id self.parent_id = self.message_id.moved_from_parent_id
message = self.message_id message = self.message_id
if message.is_moved: if message.is_moved:
self.model_record = self.env[message.moved_from_model].browse(message.moved_from_res_id)
self.model_record = self.env[message.moved_from_model].browse(
message.moved_from_res_id
)
@api.onchange('parent_id', 'model_record')
@api.onchange("parent_id", "model_record")
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
@ -144,24 +194,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.items(): for n, f in fields.items():
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):
@ -171,16 +223,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):
@ -188,44 +242,60 @@ 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:
if not r.model: if not r.model:
raise exceptions.except_orm(_('Record field is empty!'), _('Select a record for relocation first'))
raise exceptions.except_orm(
_("Record field is empty!"),
_("Select a record for relocation first"),
)
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_to_read, r.partner_id)
r.message_id.move(
r.parent_id.id,
r.res_id,
r.model,
r.move_back,
r.move_followers,
r.message_to_read,
r.partner_id,
)
if r.model in ['mail.message', 'mail.channel', False]:
if r.model in ["mail.message", "mail.channel", False]:
return { return {
'name': 'Chess game page',
'type': 'ir.actions.act_url',
'url': '/web',
'target': 'self',
"name": "Chess game page",
"type": "ir.actions.act_url",
"url": "/web",
"target": "self",
} }
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
@ -239,8 +309,10 @@ 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 {}
@ -255,20 +327,34 @@ 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_get_all_childs', help='all childs, including subchilds')
moved_as_unread = fields.Boolean('Was Unread', default=False)
_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_get_all_childs",
help="all childs, including subchilds",
)
moved_as_unread = fields.Boolean("Was Unread", default=False)
@api.multi @api.multi
def _compute_get_all_childs(self, include_myself=True): def _compute_get_all_childs(self, include_myself=True):
@ -282,30 +368,59 @@ 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, message_to_read=False, author=False):
def move(
self,
parent_id,
res_id,
model,
move_back,
move_followers=False,
message_to_read=False,
author=False,
):
for r in self: for r in self:
r.move_one(parent_id, res_id, model, move_back, move_followers=move_followers, message_to_read=message_to_read, author=author)
r.move_one(
parent_id,
res_id,
model,
move_back,
move_followers=move_followers,
message_to_read=message_to_read,
author=author,
)
@api.multi @api.multi
def move_one(self, parent_id, res_id, model, move_back, move_followers=False, message_to_read=False, author=False):
def move_one(
self,
parent_id,
res_id,
model,
move_back,
move_followers=False,
message_to_read=False,
author=False,
):
self.ensure_one() self.ensure_one()
if parent_id == self.id: if parent_id == self.id:
# if for any reason method is called to move message with parent # if for any reason method is called to move message with parent
@ -313,116 +428,126 @@ class MailMessage(models.Model):
# building message tree # building message tree
return return
if not self.author_id: if not self.author_id:
self.write({
'author_id': author.id,
})
self.write({"author_id": author.id})
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['moved_from_res_id'] = None
vals['moved_from_model'] = None
vals['moved_from_parent_id'] = None
vals['moved_as_unread'] = 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_as_unread"] = None
else: else:
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['moved_as_unread'] = message_to_read
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["moved_as_unread"] = message_to_read
# 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)
# unread message remains unread after moving back to origin # unread message remains unread after moving back to origin
if self.moved_as_unread and move_back: if self.moved_as_unread and move_back:
notification = { notification = {
'mail_message_id': self.id,
'res_partner_id': self.env.user.partner_id.id,
'is_read': False,
"mail_message_id": self.id,
"res_partner_id": self.env.user.partner_id.id,
"is_read": False,
} }
self.write({
'notification_ids': [(0, 0, notification)],
})
self.write({"notification_ids": [(0, 0, notification)]})
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 or r.env.context.get('uid')
r_vals['moved_from_res_id'] = r.res_id or r.id
r_vals['moved_from_model'] = r.model or r._name
r_vals["moved_from_parent_id"] = r.parent_id.id or r.env.context.get(
"uid"
)
r_vals["moved_from_res_id"] = r.res_id or r.id
r_vals["moved_from_model"] = r.model or r._name
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 and r.moved_from_model not in ['mail.message', 'mail.channel', False]) and r.moved_from_model
r_vals['record_name'] = r_vals['model'] and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
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
and r.moved_from_model
not in ["mail.message", "mail.channel", False]
) and r.moved_from_model
r_vals["record_name"] = (
r_vals["model"]
and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
)
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': 'record_name' in vals and vals['record_name'],
"id": self.id,
"res_id": vals.get("res_id"),
"model": vals.get("model"),
"is_moved": vals["is_moved"],
"record_name": "record_name" in vals and 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):
_inherit = 'res.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_values(self): def get_values(self):
res = super(MailMoveMessageConfiguration, self).get_values() res = super(MailMoveMessageConfiguration, self).get_values()
config_parameters = self.env["ir.config_parameter"].sudo() config_parameters = self.env["ir.config_parameter"].sudo()
model_names = config_parameters.sudo().get_param('mail_relocation_models')
model_names = model_names.split(',')
model_ids = self.env['ir.model'].sudo().search([('model', 'in', model_names)])
model_names = config_parameters.sudo().get_param("mail_relocation_models")
model_names = model_names.split(",")
model_ids = self.env["ir.model"].sudo().search([("model", "in", model_names)])
res.update( res.update(
model_ids=[m.id for m in model_ids], model_ids=[m.id for m in model_ids],
move_followers=config_parameters.sudo().get_param('mail_relocation_move_followers'),
move_followers=config_parameters.sudo().get_param(
"mail_relocation_move_followers"
),
) )
return res return res
@ -431,38 +556,50 @@ class MailMoveMessageConfiguration(models.TransientModel):
super(MailMoveMessageConfiguration, self).set_values() super(MailMoveMessageConfiguration, self).set_values()
config_parameters = self.env["ir.config_parameter"].sudo() config_parameters = self.env["ir.config_parameter"].sudo()
for record in self: for record in self:
model_names = ','.join([x.model for x in record.model_ids])
config_parameters.set_param("mail_relocation_models", model_names or '')
config_parameters.set_param("mail_relocation_move_followers", record.move_followers or '')
model_names = ",".join([x.model for x in record.model_ids])
config_parameters.set_param("mail_relocation_models", model_names or "")
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
@api.model @api.model
def default_get(self, default_fields): def default_get(self, default_fields):
contextual_self = self contextual_self = self
if 'mail_move_message' in self.env.context and self.env.context['mail_move_message']:
if (
"mail_move_message" in self.env.context
and self.env.context["mail_move_message"]
):
contextual_self = self.with_context( contextual_self = self.with_context(
default_name=self.env.context['message_name_from'] or '',
default_email=self.env.context['message_email_from'] or '',
default_name=self.env.context["message_name_from"] or "",
default_email=self.env.context["message_email_from"] or "",
) )
return super(ResPartner, contextual_self).default_get(default_fields) return super(ResPartner, contextual_self).default_get(default_fields)

135
mail_move_message/mail_move_message_views.xml

@ -1,15 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn> <!--# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
# Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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> <odoo>
<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>
@ -19,51 +24,76 @@
<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_record"/>
<label for="model_record" />
<div> <div>
<field name="model_record" class="oe_inline"/>
<field name="model" invisible="1"/>
<field name="res_id" invisible="1"/>
<field name="model_record" class="oe_inline" />
<field name="model" invisible="1" />
<field name="res_id" invisible="1" />
</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)]}"
<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" class="oe_highlight oe_inline ml32"
special="quick_create" model="res.partner" field="partner_id" context="{'force_email':True,'default_email':message_email_from,'default_name':message_name_from, 'update_message_author':True}" />
special="quick_create"
model="res.partner"
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="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">
@ -71,18 +101,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>
@ -92,30 +134,35 @@
<record id="view_mail_move_message_config_settings" model="ir.ui.view"> <record id="view_mail_move_message_config_settings" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit</field> <field name="name">res.config.settings.view.form.inherit</field>
<field name="model">res.config.settings</field> <field name="model">res.config.settings</field>
<field name="priority" eval="55"/>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="priority" eval="55" />
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside"> <xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Mail Relocation" string="Mail Relocation" data-key="mail_move_message">
<div
class="app_settings_block"
data-string="Mail Relocation"
string="Mail Relocation"
data-key="mail_move_message"
>
<h2>Mail Relocation</h2> <h2>Mail Relocation</h2>
<div class="row mt16 o_settings_container"> <div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead"> <div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead">
<div class="o_setting_left_pane"> <div class="o_setting_left_pane">
</div> </div>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<label for="model_ids"/>
<label for="model_ids" />
<div class="text-muted"> <div class="text-muted">
Add models to be used for message relocation Add models to be used for message relocation
</div> </div>
<field name="model_ids" widget="many2many_tags"/>
<field name="model_ids" widget="many2many_tags" />
</div> </div>
</div> </div>
<div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead"> <div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead">
<div class="o_setting_left_pane"> <div class="o_setting_left_pane">
<field name="move_followers"/>
<field name="move_followers" />
</div> </div>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<label for="move_followers"/>
<label for="move_followers" />
</div> </div>
</div> </div>
</div> </div>
@ -128,7 +175,7 @@
<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">res.config.settings</field> <field name="res_model">res.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>
<field name="context">{'module' : 'mail_move_message'}</field> <field name="context">{'module' : 'mail_move_message'}</field>

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;

149
mail_move_message/static/src/js/mail_move_message.js

@ -1,58 +1,58 @@
/*Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
/* Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
# Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# Copyright 2016 Pavel Romanchenko # Copyright 2016 Pavel Romanchenko
# 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_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_base.base').chat_manager;
var thread = require('mail.ChatThread');
var chatter = require('mail.Chatter');
var rpc = require('web.rpc');
var Basicmodel = require('web.BasicModel');
var view_dialogs = require('web.view_dialogs');
var field_utils_format = require('web.field_utils').format;
var BasicRenderer = require('web.BasicRenderer');
var core = require('web.core');
var form_widget = require('web.FormRenderer');
var session = require('web.Session');
var FormController = require('web.FormController');
var FormView = require('web.FormView');
var FormRenderer = require('web.FormRenderer');
var dialogs = require('web.view_dialogs');
var Dialog = require('web.Dialog');
var relational_fields = require('web.relational_fields');
var Widget = require('web.Widget');
var bus = require("bus.bus").bus;
var chat_manager = require("mail_base.base").chat_manager;
var thread = require("mail.ChatThread");
var chatter = require("mail.Chatter");
var rpc = require("web.rpc");
var Basicmodel = require("web.BasicModel");
var view_dialogs = require("web.view_dialogs");
var field_utils_format = require("web.field_utils").format;
var BasicRenderer = require("web.BasicRenderer");
var core = require("web.core");
var form_widget = require("web.FormRenderer");
var session = require("web.Session");
var FormController = require("web.FormController");
var FormView = require("web.FormView");
var FormRenderer = require("web.FormRenderer");
var dialogs = require("web.view_dialogs");
var Dialog = require("web.Dialog");
var relational_fields = require("web.relational_fields");
var Widget = require("web.Widget");
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() {},
}); });
}
},
}); });
chatter.include({ chatter.include({
@ -61,39 +61,39 @@ odoo.define('mail_move_message.relocate', function (require) {
// For show wizard in the form // For show wizard in the form
if (this.fields.thread && this.fields.thread.thread) { if (this.fields.thread && this.fields.thread.thread) {
var thread = this.fields.thread.thread; var thread = this.fields.thread.thread;
thread.on('move_message', this, thread.on_move_message);
thread.on("move_message", this, thread.on_move_message);
} }
return $.when(result).done(function() {}); return $.when(result).done(function() {});
}
},
}); });
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);
this.thread.on("move_message", this, this.thread.on_move_message);
return $.when(result).done(function() {}); return $.when(result).done(function() {});
}
},
}); });
// override methods of chat manager
// Override methods of chat manager
var chat_manager_super_make_message = chat_manager.make_message; var chat_manager_super_make_message = chat_manager.make_message;
chat_manager.make_message = function(data){
chat_manager.make_message = function(data) {
var msg = chat_manager_super_make_message(data); var msg = chat_manager_super_make_message(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;
}; };
var chat_manager_super_on_notification = chat_manager.on_notification; var chat_manager_super_on_notification = chat_manager.on_notification;
chat_manager.on_notification = function(notifications){
chat_manager.on_notification = function(notifications) {
chat_manager_super_on_notification(notifications); chat_manager_super_on_notification(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 = chat_manager.get_message(message_id); var message = 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;
@ -102,51 +102,60 @@ 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') {
_.each(message.channel_ids, function(ch){
chat_manager.bus.trigger("update_message", message);
} else if (model === "mail_move_message.delete_message") {
_.each(message.channel_ids, function(ch) {
self.remove_message_from_channel(ch, message); self.remove_message_from_channel(ch, message);
})
chat_manager.bus.trigger('update_message', message);
});
chat_manager.bus.trigger("update_message", message);
} }
}); });
}; };
Basicmodel.include({ Basicmodel.include({
applyDefaultValues: function (recordID, values, options) {
delete values.model
return this._super(recordID, values, options)
}
applyDefaultValues: function(recordID, values, options) {
delete values.model;
return this._super(recordID, values, options);
},
}); });
FormController.include({ FormController.include({
_onButtonClicked: function(event){
if(event.data.attrs.special === 'quick_create' && event.data.attrs.field === 'partner_id'){
_onButtonClicked: function(event) {
if (
event.data.attrs.special === "quick_create" &&
event.data.attrs.field === "partner_id"
) {
var self = this; var self = this;
var field_data = event.data.record.data; var field_data = event.data.record.data;
this.on_saved = function(record, bool) { this.on_saved = function(record, bool) {
var values = [{
id: record.res_id,
display_name: record.data.display_name,
}];
var values = [
{
id: record.res_id,
display_name: record.data.display_name,
},
];
}; };
var wid = self.initialState.fieldsInfo.form.partner_id.Widget; var wid = self.initialState.fieldsInfo.form.partner_id.Widget;
var relField = new relational_fields.FieldMany2One(wid,
'partner_id',
var relField = new relational_fields.FieldMany2One(
wid,
"partner_id",
self.initialState, self.initialState,
{ {
mode: 'edit',
viewType: 'form',
});
mode: "edit",
viewType: "form",
}
);
relField.getParent = function() { relField.getParent = function() {
// necessary for correct _trigger_up implementation in mixins.js
// Necessary for correct _trigger_up implementation in mixins.js
return self; return self;
}; };
var wizard_popup = relField._searchCreatePopup("form", false, { var wizard_popup = relField._searchCreatePopup("form", false, {
'message_name_from': field_data.message_name_from && field_data.message_name_from.split('@')[0],
'message_email_from': field_data.message_email_from,
'message_id': field_data.res_id,
'mail_move_message': 1,
message_name_from:
field_data.message_name_from &&
field_data.message_name_from.split("@")[0],
message_email_from: field_data.message_email_from,
message_id: field_data.res_id,
mail_move_message: 1,
}); });
} else { } else {
this._super.apply(this, arguments); this._super.apply(this, arguments);

11
mail_move_message/static/src/xml/mail_move_message_main.xml

@ -1,15 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<!--# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn> <!--# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
# Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# Copyright 2016 Pavel Romanchenko # Copyright 2016 Pavel Romanchenko
# 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).-->
<template> <template>
<t t-extend="mail.ChatThread.Message"> <t t-extend="mail.ChatThread.Message">
<t t-jquery='p.o_mail_info span:last-child i:first-child' t-operation="before"> <t t-jquery='p.o_mail_info span:last-child 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>

1
mail_move_message/tests/__init__.py

@ -1,3 +1,2 @@
# 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).
from . import test_mail_move from . import test_mail_move

13
mail_move_message/tests/test_mail_move.py

@ -8,19 +8,22 @@ 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_create_new_partner_and_move_message(self): def test_create_new_partner_and_move_message(self):
env = Environment(self.registry.test_cr, self.uid, {}) env = Environment(self.registry.test_cr, self.uid, {})
# 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
# that are returned by the backend in module_boot. Without # that are returned by the backend in module_boot. Without
# this you end up with js, css but no qweb. # this you end up with js, css but no qweb.
env['ir.module.module'].search([('name', '=', 'mail_move_message')], limit=1).state = 'installed'
env["ir.module.module"].search(
[("name", "=", "mail_move_message")], limit=1
).state = "installed"
self.registry.cursor().release() self.registry.cursor().release()
# updating models, to be able relocate messages to a partner at_install # updating models, to be able relocate messages to a partner at_install
config_parameters = self.env["ir.config_parameter"].sudo() config_parameters = self.env["ir.config_parameter"].sudo()
config_parameters.set_param("mail_relocation_models", "crm.lead,project.task,res.partner")
config_parameters.set_param(
"mail_relocation_models", "crm.lead,project.task,res.partner"
)
code = """ code = """
var delayed_button_click = function(delay, button){ var delayed_button_click = function(delay, button){
@ -49,4 +52,6 @@ class TestUi(odoo.tests.HttpCase):
console.log('ok') console.log('ok')
""" """
self.phantom_js('/web', code, login="admin", ready="$('.o_thread_icons').length")
self.phantom_js(
"/web", code, login="admin", ready="$('.o_thread_icons').length"
)

18
mail_multi_website/__init__.py

@ -13,10 +13,10 @@ def post_init_hook(cr, registry):
env.cr.execute("ALTER TABLE res_users ADD COLUMN email_multi_website VARCHAR") env.cr.execute("ALTER TABLE res_users ADD COLUMN email_multi_website VARCHAR")
# fill new email column with values from partner # fill new email column with values from partner
for user in env['res.users'].with_context(active_test=False).search([]):
for user in env["res.users"].with_context(active_test=False).search([]):
email = user.partner_id.email email = user.partner_id.email
if email: if email:
user._force_default('email_multi_website', email)
user._force_default("email_multi_website", email)
def uninstall_hook(cr, registry): def uninstall_hook(cr, registry):
@ -26,15 +26,15 @@ def uninstall_hook(cr, registry):
# remove properties # remove properties
field_ids = [ field_ids = [
env.ref('base.field_res_users_email').id,
env.ref('base.field_res_users_signature').id,
env.ref('mail.field_mail_template_body_html').id,
env.ref('mail.field_mail_template_mail_server_id').id,
env.ref('mail.field_mail_template_report_template').id,
env.ref("base.field_res_users_email").id,
env.ref("base.field_res_users_signature").id,
env.ref("mail.field_mail_template_body_html").id,
env.ref("mail.field_mail_template_mail_server_id").id,
env.ref("mail.field_mail_template_report_template").id,
] ]
env['ir.property'].search([('fields_id', 'in', field_ids)]).unlink()
env["ir.property"].search([("fields_id", "in", field_ids)]).unlink()
# copy emails from partner to user # copy emails from partner to user
cr.execute("SELECT partner_id,email_multi_website FROM res_users") cr.execute("SELECT partner_id,email_multi_website FROM res_users")
for partner_id, default_email in cr.fetchall(): for partner_id, default_email in cr.fetchall():
env['res.partner'].browse(partner_id).email = default_email
env["res.partner"].browse(partner_id).email = default_email

21
mail_multi_website/__manifest__.py

@ -8,36 +8,23 @@
"images": ["images/main.jpg"], "images": ["images/main.jpg"],
"version": "11.0.1.0.0", "version": "11.0.1.0.0",
"application": False, "application": False,
"author": "IT-Projects LLC, Ivan Yelizariev", "author": "IT-Projects LLC, Ivan Yelizariev",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info/team/yelizariev", "website": "https://it-projects.info/team/yelizariev",
"license": "LGPL-3", "license": "LGPL-3",
"price": 230.00, "price": 230.00,
"currency": "EUR", "currency": "EUR",
"depends": [
"ir_config_parameter_multi_company",
"web_website",
"mail",
],
"depends": ["ir_config_parameter_multi_company", "web_website", "mail"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
"views/website_views.xml",
],
"demo": [
],
"qweb": [
],
"data": ["views/website_views.xml"],
"demo": [],
"qweb": [],
"post_load": None, "post_load": None,
"pre_init_hook": None, "pre_init_hook": None,
"post_init_hook": "post_init_hook", "post_init_hook": "post_init_hook",
"uninstall_hook": "uninstall_hook", "uninstall_hook": "uninstall_hook",
"auto_install": False, "auto_install": False,
"installable": True, "installable": True,
# "demo_title": "Email Addresses per Website", # "demo_title": "Email Addresses per Website",
# "demo_addons": [ # "demo_addons": [
# ], # ],

2
mail_multi_website/doc/index.rst

@ -53,7 +53,7 @@ Configure ``mail.catchall.domain`` per website. See Documentation of the module
Outgoing mails servers per website Outgoing mails servers per website
-------------------------- --------------------------
If each domain has different Outgoing Mail Server you need following adjustments
If each domain has different Outgoing Mail Server you need following adjustments
* Got to menu ``[[ Website ]] >> Configuration >> Websites`` * Got to menu ``[[ Website ]] >> Configuration >> Websites``
* In each Website specify field **Outgoing Mails** * In each Website specify field **Outgoing Mails**

12
mail_multi_website/models/ir_property.py

@ -1,19 +1,19 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
from odoo import models, api
from odoo import api, models
class IrProperty(models.Model): class IrProperty(models.Model):
_inherit = 'ir.property'
_inherit = "ir.property"
@api.multi @api.multi
def write(self, vals): def write(self, vals):
res = super(IrProperty, self).write(vals) res = super(IrProperty, self).write(vals)
field_object_list = [ field_object_list = [
self.env.ref('base.field_res_users_email'),
self.env.ref('mail.field_mail_template_body_html'),
self.env.ref('mail.field_mail_template_mail_server_id'),
self.env.ref('mail.field_mail_template_report_template'),
self.env.ref("base.field_res_users_email"),
self.env.ref("mail.field_mail_template_body_html"),
self.env.ref("mail.field_mail_template_mail_server_id"),
self.env.ref("mail.field_mail_template_report_template"),
] ]
for fobj in field_object_list: for fobj in field_object_list:
self._update_db_value_website_dependent(fobj) self._update_db_value_website_dependent(fobj)

8
mail_multi_website/models/mail_message.py

@ -1,16 +1,16 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
from odoo import models, fields
from odoo import fields, models
class Message(models.Model): class Message(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
def _default_mail_server_id(self): def _default_mail_server_id(self):
website = self.env.context.get('website_id')
website = self.env.context.get("website_id")
if not website: if not website:
return return
website = self.env['website'].sudo().browse(website)
website = self.env["website"].sudo().browse(website)
return website.mail_server_id.id return website.mail_server_id.id
mail_server_id = fields.Many2one(default=_default_mail_server_id) mail_server_id = fields.Many2one(default=_default_mail_server_id)

112
mail_multi_website/models/mail_template.py

@ -1,28 +1,41 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
import logging import logging
from odoo import models, fields, api, tools, _
from odoo import _, api, fields, models, tools
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import pycompat from odoo.tools import pycompat
from odoo.addons.mail.models.mail_template import format_date, format_tz, format_amount
from odoo.addons.mail.models.mail_template import format_amount, format_date, format_tz
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
FIELDS = ['body_html', 'mail_server_id', 'report_template']
FIELDS = ["body_html", "mail_server_id", "report_template"]
try: try:
from odoo.addons.mail.models.mail_template import mako_safe_template_env, mako_template_env
from odoo.addons.mail.models.mail_template import (
mako_safe_template_env,
mako_template_env,
)
except ImportError: except ImportError:
_logger.warning("jinja2 not available, templating features will not work!") _logger.warning("jinja2 not available, templating features will not work!")
class MailTemplate(models.Model): class MailTemplate(models.Model):
_inherit = ['mail.template', 'website_dependent.mixin']
_name = 'mail.template'
_inherit = ["mail.template", "website_dependent.mixin"]
_name = "mail.template"
body_html = fields.Html(company_dependent=True, website_dependent=True) body_html = fields.Html(company_dependent=True, website_dependent=True)
mail_server_id = fields.Many2one(string='Outgoing Mail Server (Multi-Website)', company_dependent=True, website_dependent=True)
report_template = fields.Many2one(string='Optional report to print and attach (Multi-Website)', company_dependent=True, website_dependent=True)
mail_server_id = fields.Many2one(
string="Outgoing Mail Server (Multi-Website)",
company_dependent=True,
website_dependent=True,
)
report_template = fields.Many2one(
string="Optional report to print and attach (Multi-Website)",
company_dependent=True,
website_dependent=True,
)
@api.multi @api.multi
def generate_email(self, res_ids, fields=None): def generate_email(self, res_ids, fields=None):
@ -37,8 +50,8 @@ class MailTemplate(models.Model):
list_of_dict = res list_of_dict = res
for _unused, data in list_of_dict.items(): for _unused, data in list_of_dict.items():
if 'mail_server_id' in data and not data.get('mail_server_id'):
del data['mail_server_id']
if "mail_server_id" in data and not data.get("mail_server_id"):
del data["mail_server_id"]
return res return res
@ -54,67 +67,93 @@ class MailTemplate(models.Model):
# try to load the template # try to load the template
try: try:
mako_env = mako_safe_template_env if self.env.context.get('safe') else mako_template_env
mako_env = (
mako_safe_template_env
if self.env.context.get("safe")
else mako_template_env
)
template = mako_env.from_string(tools.ustr(template_txt)) template = mako_env.from_string(tools.ustr(template_txt))
except Exception: except Exception:
_logger.info("Failed to load template %r", template_txt, exc_info=True) _logger.info("Failed to load template %r", template_txt, exc_info=True)
return multi_mode and results or results[res_ids[0]] return multi_mode and results or results[res_ids[0]]
# prepare template variables # prepare template variables
records = self.env[model].browse(it for it in res_ids if it) # filter to avoid browsing [None]
records = self.env[model].browse(
it for it in res_ids if it
) # filter to avoid browsing [None]
res_to_rec = dict.fromkeys(res_ids, None) res_to_rec = dict.fromkeys(res_ids, None)
for record in records: for record in records:
res_to_rec[record.id] = record res_to_rec[record.id] = record
variables = { variables = {
'format_date': lambda date, format=False, context=self._context: format_date(self.env, date, format),
'format_tz': lambda dt, tz=False, format=False, context=self._context: format_tz(self.env, dt, tz, format),
'format_amount': lambda amount, currency, context=self._context: format_amount(self.env, amount, currency),
'user': self.env.user,
'ctx': self._context, # context kw would clash with mako internals
"format_date": lambda date, format=False, context=self._context: format_date(
self.env, date, format
),
"format_tz": lambda dt, tz=False, format=False, context=self._context: format_tz(
self.env, dt, tz, format
),
"format_amount": lambda amount, currency, context=self._context: format_amount(
self.env, amount, currency
),
"user": self.env.user,
"ctx": self._context, # context kw would clash with mako internals
} }
# [NEW] Check website and company context # [NEW] Check website and company context
company = self.env['res.company'] # empty value
company = self.env["res.company"] # empty value
company_id = self.env.context.get('force_company')
company_id = self.env.context.get("force_company")
if company_id: if company_id:
company = self.env['res.company'].sudo().browse(company_id)
company = self.env["res.company"].sudo().browse(company_id)
if self.env.context.get('website_id'):
website = self.env['website'].browse(self.env.context.get('website_id'))
if self.env.context.get("website_id"):
website = self.env["website"].browse(self.env.context.get("website_id"))
else: else:
website = self.env.user.backend_website_id website = self.env.user.backend_website_id
for res_id, record in res_to_rec.items(): for res_id, record in res_to_rec.items():
record_company = company record_company = company
if not record_company: if not record_company:
if hasattr(record, 'company_id') and record.company_id:
if hasattr(record, "company_id") and record.company_id:
record_company = record.company_id record_company = record.company_id
record_website = website record_website = website
if hasattr(record, 'website_id') and record.website_id:
if hasattr(record, "website_id") and record.website_id:
record_website = record.website_id record_website = record.website_id
if record_company and record_website \
and record_website.company_id != company:
if (
record_company
and record_website
and record_website.company_id != company
):
# company and website are incompatible, so keep only company # company and website are incompatible, so keep only company
record_website = self.env['website'] # empty value
record_website = self.env["website"] # empty value
record_context = dict(force_company=record_company.id, website_id=record_website.id)
variables['object'] = record.with_context(**record_context)
variables['website'] = record_website
record_context = dict(
force_company=record_company.id, website_id=record_website.id
)
variables["object"] = record.with_context(**record_context)
variables["website"] = record_website
try: try:
render_result = template.render(variables) render_result = template.render(variables)
except Exception: except Exception:
_logger.info("Failed to render template %r using values %r" % (template, variables), exc_info=True)
raise UserError(_("Failed to render template %r using values %r") % (template, variables))
_logger.info(
"Failed to render template %r using values %r"
% (template, variables),
exc_info=True,
)
raise UserError(
_("Failed to render template %r using values %r")
% (template, variables)
)
if render_result == u"False": if render_result == u"False":
render_result = u"" render_result = u""
if post_process: if post_process:
render_result = self.with_context(**record_context).render_post_process(render_result)
render_result = self.with_context(**record_context).render_post_process(
render_result
)
results[res_id] = render_result results[res_id] = render_result
@ -133,13 +172,10 @@ class MailTemplate(models.Model):
res = super(MailTemplate, self).write(vals) res = super(MailTemplate, self).write(vals)
# TODO: will it work with OCA's partner_firstname module? # TODO: will it work with OCA's partner_firstname module?
if 'name' in vals:
if "name" in vals:
fields_to_update = FIELDS fields_to_update = FIELDS
else: else:
fields_to_update = [
f for f in FIELDS
if f in vals
]
fields_to_update = [f for f in FIELDS if f in vals]
for f in fields_to_update: for f in fields_to_update:
self._update_properties_label(f) self._update_properties_label(f)

29
mail_multi_website/models/mail_thread.py

@ -1,23 +1,28 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
from odoo import models, api, tools
from odoo import api, models, tools
class MailThread(models.AbstractModel): class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
_inherit = "mail.thread"
@api.model @api.model
def message_route_process(self, message, message_dict, routes): def message_route_process(self, message, message_dict, routes):
rcpt_tos = ','.join([
tools.decode_message_header(message, 'Delivered-To'),
tools.decode_message_header(message, 'To'),
tools.decode_message_header(message, 'Cc'),
tools.decode_message_header(message, 'Resent-To'),
tools.decode_message_header(message, 'Resent-Cc')])
rcpt_tos_websiteparts = [e.split('@')[1].lower() for e in tools.email_split(rcpt_tos)]
website = self.env['website'].sudo().search([
('domain', 'in', rcpt_tos_websiteparts)
])
rcpt_tos = ",".join(
[
tools.decode_message_header(message, "Delivered-To"),
tools.decode_message_header(message, "To"),
tools.decode_message_header(message, "Cc"),
tools.decode_message_header(message, "Resent-To"),
tools.decode_message_header(message, "Resent-Cc"),
]
)
rcpt_tos_websiteparts = [
e.split("@")[1].lower() for e in tools.email_split(rcpt_tos)
]
website = (
self.env["website"].sudo().search([("domain", "in", rcpt_tos_websiteparts)])
)
if website: if website:
self = self.with_context(website_id=website[0].id) self = self.with_context(website_id=website[0].id)

16
mail_multi_website/models/res_users.py

@ -1,31 +1,31 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
import logging import logging
from odoo import models, fields, api
from odoo import api, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
FIELD_NAME = 'email_multi_website'
FIELDS = ['signature']
FIELD_NAME = "email_multi_website"
FIELDS = ["signature"]
ALL_FIELDS = [FIELD_NAME] + FIELDS ALL_FIELDS = [FIELD_NAME] + FIELDS
class User(models.Model): class User(models.Model):
_inherit = ['res.users', 'website_dependent.mixin']
_name = 'res.users'
_inherit = ["res.users", "website_dependent.mixin"]
_name = "res.users"
signature = fields.Html(company_dependent=True, website_dependent=True) signature = fields.Html(company_dependent=True, website_dependent=True)
# extra field to detach email field from res.partner # extra field to detach email field from res.partner
email = fields.Char(related='email_multi_website', inherited=False)
email = fields.Char(related="email_multi_website", inherited=False)
email_multi_website = fields.Char(company_dependent=True, website_dependent=True) email_multi_website = fields.Char(company_dependent=True, website_dependent=True)
@api.model @api.model
def create(self, vals): def create(self, vals):
res = super(User, self).create(vals) res = super(User, self).create(vals)
# make value company independent # make value company independent
res._force_default(FIELD_NAME, vals.get('email'))
res._force_default(FIELD_NAME, vals.get("email"))
for f in FIELDS: for f in FIELDS:
res._force_default(f, vals.get(f)) res._force_default(f, vals.get(f))
return res return res
@ -34,7 +34,7 @@ class User(models.Model):
def write(self, vals): def write(self, vals):
res = super(User, self).write(vals) res = super(User, self).write(vals)
# TODO: will it work with OCA's partner_firstname module? # TODO: will it work with OCA's partner_firstname module?
if any(k in vals for k in ['name', 'email'] + FIELDS):
if any(k in vals for k in ["name", "email"] + FIELDS):
for f in ALL_FIELDS: for f in ALL_FIELDS:
self._update_properties_label(f) self._update_properties_label(f)
return res return res

6
mail_multi_website/models/website.py

@ -1,10 +1,12 @@
# Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import models, fields
from odoo import fields, models
class Website(models.Model): class Website(models.Model):
_inherit = "website" _inherit = "website"
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing Mails', help='Default outgoing mail server')
mail_server_id = fields.Many2one(
"ir.mail_server", "Outgoing Mails", help="Default outgoing mail server"
)

53
mail_multi_website/tests/test_fetch.py

@ -1,7 +1,8 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
from odoo.addons.mail.tests.common import TestMail
from odoo.tools import mute_logger from odoo.tools import mute_logger
from odoo.addons.mail.tests.common import TestMail
from odoo.addons.mail.tests.test_mail_gateway import MAIL_TEMPLATE from odoo.addons.mail.tests.test_mail_gateway import MAIL_TEMPLATE
@ -11,31 +12,45 @@ class TestFetch(TestMail):
def setUp(self): def setUp(self):
super(TestFetch, self).setUp() super(TestFetch, self).setUp()
self.website = self.env['website'].create({
'name': 'Test Website',
'domain': 'example.com',
})
self.company = self.env['res.company'].create({
'name': 'New Test Website'
})
self.website = self.env["website"].create(
{"name": "Test Website", "domain": "example.com"}
)
self.company = self.env["res.company"].create({"name": "New Test Website"})
self.website.company_id = self.company self.website.company_id = self.company
# copy-paste from mail.tests.test_mail_gateway # copy-paste from mail.tests.test_mail_gateway
mail_test_model = self.env['ir.model']._get('mail.test')
mail_test_model = self.env["ir.model"]._get("mail.test")
# groups@.. will cause the creation of new mail.test # groups@.. will cause the creation of new mail.test
self.alias = self.env['mail.alias'].create({
'alias_name': 'groups',
'alias_user_id': False,
'alias_model_id': mail_test_model.id,
'alias_contact': 'everyone'})
self.alias = self.env["mail.alias"].create(
{
"alias_name": "groups",
"alias_user_id": False,
"alias_model_id": mail_test_model.id,
"alias_contact": "everyone",
}
)
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
@mute_logger("odoo.addons.mail.models.mail_thread", "odoo.models")
def test_fetch_multi_website(self): def test_fetch_multi_website(self):
""" Incoming email on an alias creating a new record + message_new + message details """ """ Incoming email on an alias creating a new record + message_new + message details """
new_groups = self.format_and_process(MAIL_TEMPLATE, subject='My Frogs', to='groups@example.com, other@gmail.com')
new_groups = self.format_and_process(
MAIL_TEMPLATE, subject="My Frogs", to="groups@example.com, other@gmail.com"
)
# Test: one group created by mailgateway administrator # Test: one group created by mailgateway administrator
self.assertEqual(len(new_groups), 1, 'message_process: a new mail.test should have been created')
self.assertEqual(
len(new_groups),
1,
"message_process: a new mail.test should have been created",
)
self.assertEqual(new_groups.website_id, self.website, 'New record is created with wrong website')
self.assertEqual(new_groups.company_id, self.company, 'New record is created with wrong company')
self.assertEqual(
new_groups.website_id,
self.website,
"New record is created with wrong website",
)
self.assertEqual(
new_groups.company_id,
self.company,
"New record is created with wrong company",
)

16
mail_multi_website/tests/test_mail_model.py

@ -1,10 +1,18 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
from odoo import models, fields
from odoo import fields, models
class MailTest(models.Model): class MailTest(models.Model):
_inherit = 'mail.test'
_inherit = "mail.test"
company_id = fields.Many2one('res.company', default=lambda self: self.env['res.company']._company_default_get())
website_id = fields.Many2one('website', default=lambda self: self.env['website'].browse(self.env.context.get('website_id')))
company_id = fields.Many2one(
"res.company",
default=lambda self: self.env["res.company"]._company_default_get(),
)
website_id = fields.Many2one(
"website",
default=lambda self: self.env["website"].browse(
self.env.context.get("website_id")
),
)

116
mail_multi_website/tests/test_render.py

@ -14,58 +14,66 @@ class TestRender(TestMail):
self.original_email = self.env.user.email self.original_email = self.env.user.email
self.original_company = self.env.user.company_id self.original_company = self.env.user.company_id
self.email = 'superadmin@second-website.example'
self.email = "superadmin@second-website.example"
self.assertNotEqual(self.original_email, self.email) self.assertNotEqual(self.original_email, self.email)
self.website = self.env.ref('website.website2')
self.company = self.env['res.company'].create({
'name': 'New Test Website'
})
self.website = self.env.ref("website.website2")
self.company = self.env["res.company"].create({"name": "New Test Website"})
self.website.company_id = self.company self.website.company_id = self.company
self.mail_server_id = self.env['ir.mail_server'].create({
'name': 'mail server',
'smtp_host': 'mail.example.com',
})
self.mail_server_id = self.env["ir.mail_server"].create(
{"name": "mail server", "smtp_host": "mail.example.com"}
)
self.website.mail_server_id = self.mail_server_id self.website.mail_server_id = self.mail_server_id
# copy-paste from mail.tests.test_mail_template # copy-paste from mail.tests.test_mail_template
self._attachments = [{
'name': '_Test_First',
'datas_fname':
'first.txt',
'datas': base64.b64encode(b'My first attachment'),
'res_model': 'res.partner',
'res_id': self.user_admin.partner_id.id
}, {
'name': '_Test_Second',
'datas_fname': 'second.txt',
'datas': base64.b64encode(b'My second attachment'),
'res_model': 'res.partner',
'res_id': self.user_admin.partner_id.id
}]
self.email_1 = 'test1@example.com'
self.email_2 = 'test2@example.com'
self._attachments = [
{
"name": "_Test_First",
"datas_fname": "first.txt",
"datas": base64.b64encode(b"My first attachment"),
"res_model": "res.partner",
"res_id": self.user_admin.partner_id.id,
},
{
"name": "_Test_Second",
"datas_fname": "second.txt",
"datas": base64.b64encode(b"My second attachment"),
"res_model": "res.partner",
"res_id": self.user_admin.partner_id.id,
},
]
self.email_1 = "test1@example.com"
self.email_2 = "test2@example.com"
self.email_3 = self.partner_1.email self.email_3 = self.partner_1.email
self.email_template = self.env['mail.template'].create({
'model_id': self.env['ir.model']._get('mail.test').id,
'name': 'Pigs Template',
'subject': '${website.name}',
'body_html': '${object.description}',
'user_signature': False,
'attachment_ids': [(0, 0, self._attachments[0]), (0, 0, self._attachments[1])],
'partner_to': '%s,%s' % (self.partner_2.id, self.user_employee.partner_id.id),
'email_to': '%s, %s' % (self.email_1, self.email_2),
'email_cc': '%s' % self.email_3})
self.email_template = self.env["mail.template"].create(
{
"model_id": self.env["ir.model"]._get("mail.test").id,
"name": "Pigs Template",
"subject": "${website.name}",
"body_html": "${object.description}",
"user_signature": False,
"attachment_ids": [
(0, 0, self._attachments[0]),
(0, 0, self._attachments[1]),
],
"partner_to": "%s,%s"
% (self.partner_2.id, self.user_employee.partner_id.id),
"email_to": "{}, {}".format(self.email_1, self.email_2),
"email_cc": "%s" % self.email_3,
}
)
def switch_user_website(self): def switch_user_website(self):
# add website to allowed # add website to allowed
self.env.user.write(dict(
backend_website_ids=[(4, self.website.id)],
backend_website_id=self.website.id,
company_id=self.company.id,
company_ids=[(4, self.company.id)]
))
self.env.user.write(
dict(
backend_website_ids=[(4, self.website.id)],
backend_website_id=self.website.id,
company_id=self.company.id,
company_ids=[(4, self.company.id)],
)
)
def test_website_in_render_variables(self): def test_website_in_render_variables(self):
"""Mail values are per website""" """Mail values are per website"""
@ -74,26 +82,30 @@ class TestRender(TestMail):
# sending without website # sending without website
mail_id = self.email_template.send_mail(self.test_pigs.id) mail_id = self.email_template.send_mail(self.test_pigs.id)
mail = self.env['mail.mail'].browse(mail_id)
self.assertEqual(mail.subject, '')
mail = self.env["mail.mail"].browse(mail_id)
self.assertEqual(mail.subject, "")
self.assertFalse(mail.mail_server_id) self.assertFalse(mail.mail_server_id)
# sending from frontend # sending from frontend
self.test_pigs.company_id = None self.test_pigs.company_id = None
mail_id = self.email_template.with_context(wdb=True, website_id=self.website.id).send_mail(self.test_pigs.id)
mail = self.env['mail.mail'].browse(mail_id)
mail_id = self.email_template.with_context(
wdb=True, website_id=self.website.id
).send_mail(self.test_pigs.id)
mail = self.env["mail.mail"].browse(mail_id)
self.assertEqual(mail.subject, self.website.name) self.assertEqual(mail.subject, self.website.name)
self.assertEqual(mail.mail_server_id, self.mail_server_id) self.assertEqual(mail.mail_server_id, self.mail_server_id)
# copy-pasted tests # copy-pasted tests
self.assertEqual(mail.email_to, self.email_template.email_to) self.assertEqual(mail.email_to, self.email_template.email_to)
self.assertEqual(mail.email_cc, self.email_template.email_cc) self.assertEqual(mail.email_cc, self.email_template.email_cc)
self.assertEqual(mail.recipient_ids, self.partner_2 | self.user_employee.partner_id)
self.assertEqual(
mail.recipient_ids, self.partner_2 | self.user_employee.partner_id
)
# sending from frontend # sending from frontend
self.switch_user_website() self.switch_user_website()
mail_id = self.email_template.send_mail(self.test_pigs.id) mail_id = self.email_template.send_mail(self.test_pigs.id)
mail = self.env['mail.mail'].browse(mail_id)
mail = self.env["mail.mail"].browse(mail_id)
self.assertEqual(mail.subject, self.website.name) self.assertEqual(mail.subject, self.website.name)
def _test_message_post_with_template(self): def _test_message_post_with_template(self):
@ -109,6 +121,8 @@ class TestRender(TestMail):
self.env.user.invalidate_cache() self.env.user.invalidate_cache()
self.assertEqual(self.env.user.email, self.original_email) self.assertEqual(self.env.user.email, self.original_email)
self.test_pigs.with_context(website_id=self.website.id).message_post_with_template(self.email_template.id)
message = self.env['mail.message'].search([], order='id desc', limit=1)
self.assertIn('<%s>' % self.email, message.email_from)
self.test_pigs.with_context(
website_id=self.website.id
).message_post_with_template(self.email_template.id)
message = self.env["mail.message"].search([], order="id desc", limit=1)
self.assertIn("<%s>" % self.email, message.email_from)

49
mail_multi_website/tests/test_send.py

@ -9,13 +9,11 @@ class TestSendMail(TransactionCase):
def setUp(self): def setUp(self):
super(TestSendMail, self).setUp() super(TestSendMail, self).setUp()
self.website = self.env.ref('website.website2')
self.company = self.env['res.company'].create({
'name': 'New Test Website'
})
self.website = self.env.ref("website.website2")
self.company = self.env["res.company"].create({"name": "New Test Website"})
self.original_email = self.env.user.email self.original_email = self.env.user.email
self.original_company = self.env.user.company_id self.original_company = self.env.user.company_id
self.email = 'superadmin@second-website.example'
self.email = "superadmin@second-website.example"
# Check that current email is set and differs # Check that current email is set and differs
self.assertTrue(self.email) self.assertTrue(self.email)
self.assertNotEqual(self.original_email, self.email) self.assertNotEqual(self.original_email, self.email)
@ -23,12 +21,14 @@ class TestSendMail(TransactionCase):
def switch_user_website(self): def switch_user_website(self):
# add website to allowed # add website to allowed
self.env.user.write(dict(
backend_website_ids=[(4, self.website.id)],
backend_website_id=self.website.id,
company_id=self.company.id,
company_ids=[(4, self.company.id)]
))
self.env.user.write(
dict(
backend_website_ids=[(4, self.website.id)],
backend_website_id=self.website.id,
company_id=self.company.id,
company_ids=[(4, self.company.id)],
)
)
def test_multi_email(self): def test_multi_email(self):
"""User has email addresses per website""" """User has email addresses per website"""
@ -37,21 +37,28 @@ class TestSendMail(TransactionCase):
self.env.user.email = self.email self.env.user.email = self.email
# Check that writing works # Check that writing works
self.env.user.invalidate_cache() self.env.user.invalidate_cache()
self.assertEqual(self.env.user.email, self.email, 'Write methods doesn\'t work (Field is not in registry?)')
self.assertEqual(
self.env.user.email,
self.email,
"Write methods doesn't work (Field is not in registry?)",
)
# changing company will automatically update website value to empty value # changing company will automatically update website value to empty value
self.env.user.company_id = self.original_company self.env.user.company_id = self.original_company
self.env.user.invalidate_cache() self.env.user.invalidate_cache()
self.assertEqual(self.env.user.email, self.original_email, 'Multi-email doesn\'t work on switching websites')
self.assertEqual(
self.env.user.email,
self.original_email,
"Multi-email doesn't work on switching websites",
)
def test_multi_email_partner(self): def test_multi_email_partner(self):
"""Partner doesn't have email addresses per website""" """Partner doesn't have email addresses per website"""
original_email = 'original@email1'
new_email = 'new@email2'
partner = self.env['res.partner'].create({
'name': 'test',
'email': original_email,
})
original_email = "original@email1"
new_email = "new@email2"
partner = self.env["res.partner"].create(
{"name": "test", "email": original_email}
)
self.switch_user_website() self.switch_user_website()
# update partner's email # update partner's email
partner.email = new_email partner.email = new_email
@ -59,4 +66,6 @@ class TestSendMail(TransactionCase):
# changing company will automatically update website value to empty value # changing company will automatically update website value to empty value
self.env.user.company_id = self.original_company self.env.user.company_id = self.original_company
self.env.user.invalidate_cache() self.env.user.invalidate_cache()
self.assertEqual(partner.email, new_email, 'Partner\'s email must not be Multi-website')
self.assertEqual(
partner.email, new_email, "Partner's email must not be Multi-website"
)

6
mail_multi_website/views/website_views.xml

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> <!-- Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). --> License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -->
<odoo> <odoo>
<record id="view_website_multi_mail_form" model="ir.ui.view"> <record id="view_website_multi_mail_form" model="ir.ui.view">
<field name="model">website</field> <field name="model">website</field>
<field name="inherit_id" ref="website.view_website_form"/>
<field name="inherit_id" ref="website.view_website_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='default_lang_id']" position="after"> <xpath expr="//field[@name='default_lang_id']" position="after">
<field name="mail_server_id"/>
<field name="mail_server_id" />
</xpath> </xpath>
</field> </field>
</record> </record>

12
mail_multi_website/wizard/mail_compose_message.py

@ -1,20 +1,22 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> # Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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).
from odoo import models, api
from odoo import api, models
from odoo.http import request from odoo.http import request
class MailComposer(models.TransientModel): class MailComposer(models.TransientModel):
_inherit = 'mail.compose.message'
_inherit = "mail.compose.message"
@api.model @api.model
def create(self, vals): def create(self, vals):
"""Workaround for https://github.com/odoo/odoo/pull/26589""" """Workaround for https://github.com/odoo/odoo/pull/26589"""
if 'website_id' not in self.env.context:
website = request and hasattr(request, 'website') and request.website or None
if "website_id" not in self.env.context:
website = (
request and hasattr(request, "website") and request.website or None
)
if not website: if not website:
website = self.env['website'].get_current_website()
website = self.env["website"].get_current_website()
if website: if website:
self = self.with_context(website_id=website.id) self = self.with_context(website_id=website.id)
return super(MailComposer, self).create(vals) return super(MailComposer, self).create(vals)

21
mail_private/__manifest__.py

@ -10,36 +10,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": "11.0.1.2.0", "version": "11.0.1.2.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>

130
mail_private/models.py

@ -4,22 +4,22 @@
# 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")
class MailMessage(models.Model): class MailMessage(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
is_private = fields.Boolean(string='Send Internal Message')
is_private = fields.Boolean(string="Send Internal Message")
def send_recepients_for_internal_message(self, model, domain): def send_recepients_for_internal_message(self, model, domain):
result = {'partners': [], 'channels': []}
result = {"partners": [], "channels": []}
default_resource = self.env[model].search(domain) default_resource = self.env[model].search(domain)
follower_ids = default_resource.message_follower_ids follower_ids = default_resource.message_follower_ids
@ -27,35 +27,44 @@ class MailMessage(models.Model):
channel_ids = [c.channel_id for c in follower_ids if c.channel_id] channel_ids = [c.channel_id for c in follower_ids if c.channel_id]
for recipient in recipient_ids: for recipient in recipient_ids:
result['partners'].append({
'checked': recipient.user_ids.id and not any(recipient.user_ids.mapped('share')),
'partner_id': recipient.id,
'full_name': recipient.name,
'name': recipient.name,
'email_address': recipient.email,
'reason': 'Recipient'
})
result["partners"].append(
{
"checked": recipient.user_ids.id
and not any(recipient.user_ids.mapped("share")),
"partner_id": recipient.id,
"full_name": recipient.name,
"name": recipient.name,
"email_address": recipient.email,
"reason": "Recipient",
}
)
for channel in channel_ids: for channel in channel_ids:
result['channels'].append({
'checked': True,
'channel_id': channel.id,
'full_name': channel.name,
'name': '# '+channel.name,
'reason': 'Channel',
})
result["channels"].append(
{
"checked": True,
"channel_id": channel.id,
"full_name": channel.name,
"name": "# " + channel.name,
"reason": "Channel",
}
)
return result return result
@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 not self_sudo.is_private: if not self_sudo.is_private:
super(MailMessage, self)._notify(force_send, send_after_commit, user_signature)
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
):
""" Compute recipients to notify based on specified recipients and document """ Compute recipients to notify based on specified recipients and document
followers. Delegate notification to partners to send emails and bus notifications followers. Delegate notification to partners to send emails and bus notifications
and to channels to broadcast messages on channels """ and to channels to broadcast messages on channels """
@ -67,17 +76,25 @@ class MailMessage(models.Model):
channels_sudo = self_sudo.channel_ids channels_sudo = self_sudo.channel_ids
# remove author from notified partners # remove author from notified partners
if not self._context.get('mail_notify_author', False) and self_sudo.author_id:
if not self._context.get("mail_notify_author", False) and self_sudo.author_id:
partners_sudo = partners_sudo - self_sudo.author_id partners_sudo = partners_sudo - self_sudo.author_id
# update message, with maybe custom valuesz # update message, with maybe custom valuesz
message_values = {} message_values = {}
if channels_sudo: if channels_sudo:
message_values['channel_ids'] = [(6, 0, channels_sudo.ids)]
message_values["channel_ids"] = [(6, 0, channels_sudo.ids)]
if partners_sudo: if partners_sudo:
message_values['needaction_partner_ids'] = [(6, 0, partners_sudo.ids)]
if self.model and self.res_id and hasattr(self.env[self.model], 'message_get_message_notify_values'):
message_values.update(self.env[self.model].browse(self.res_id).message_get_message_notify_values(self, message_values))
message_values["needaction_partner_ids"] = [(6, 0, partners_sudo.ids)]
if (
self.model
and self.res_id
and hasattr(self.env[self.model], "message_get_message_notify_values")
):
message_values.update(
self.env[self.model]
.browse(self.res_id)
.message_get_message_notify_values(self, message_values)
)
if message_values: if message_values:
self.write(message_values) self.write(message_values)
@ -85,14 +102,23 @@ class MailMessage(models.Model):
# those methods are called as SUPERUSER because portal users posting messages # those methods are called as SUPERUSER because portal users posting messages
# have no access to partner model. Maybe propagating a real uid could be necessary. # have no access to partner model. Maybe propagating a real uid could be necessary.
email_channels = channels_sudo.filtered(lambda channel: channel.email_send) email_channels = channels_sudo.filtered(lambda channel: channel.email_send)
notif_partners = partners_sudo.filtered(lambda partner: 'inbox' in partner.mapped('user_ids.notification_type'))
notif_partners = partners_sudo.filtered(
lambda partner: "inbox" in partner.mapped("user_ids.notification_type")
)
if email_channels or partners_sudo - notif_partners: if email_channels or partners_sudo - notif_partners:
partners_sudo.search([
'|',
('id', 'in', (partners_sudo - notif_partners).ids),
('channel_ids', 'in', email_channels.ids),
('email', '!=', self_sudo.author_id.email or self_sudo.email_from),
])._notify(self, force_send=force_send, send_after_commit=send_after_commit, user_signature=user_signature)
partners_sudo.search(
[
"|",
("id", "in", (partners_sudo - notif_partners).ids),
("channel_ids", "in", email_channels.ids),
("email", "!=", self_sudo.author_id.email or self_sudo.email_from),
]
)._notify(
self,
force_send=force_send,
send_after_commit=send_after_commit,
user_signature=user_signature,
)
channels_sudo._notify(self) channels_sudo._notify(self)
# Discard cache, because child / parent allow reading and therefore # Discard cache, because child / parent allow reading and therefore
@ -104,14 +130,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
)

9
mail_private/static/src/css/mail_private.css

@ -1,10 +1,9 @@
.o_composer_suggested_channels{
.o_composer_suggested_channels {
margin-top: -5px; margin-top: -5px;
} }
.oe_composer_uncheck{
.oe_composer_uncheck {
float: bottom; float: bottom;
margin-top:5px;
margin-left:-5px;
margin-top: 5px;
margin-left: -5px;
} }

498
mail_private/static/src/js/mail_private.js

@ -4,243 +4,295 @@
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';
var core = require('web.core');
var Chatter = require('mail.Chatter');
var ChatterComposer = require('mail.ChatterComposer');
var chat_manager = require('mail_base.base').chat_manager;
var session = require('web.session');
var rpc = require('web.rpc');
var config = require('web.config');
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';
},
on_open_composer_private_message: function (event) {
var self = this;
this.fetch_recipients_for_internal_message().then(function (data) {
self._openComposer({
odoo.define("mail_private", function(require) {
"use strict";
var core = require("web.core");
var Chatter = require("mail.Chatter");
var ChatterComposer = require("mail.ChatterComposer");
var chat_manager = require("mail_base.base").chat_manager;
var session = require("web.session");
var rpc = require("web.rpc");
var config = require("web.config");
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";
},
on_open_composer_private_message: function(event) {
var self = this;
this.fetch_recipients_for_internal_message().then(function(data) {
self._openComposer({
is_private: true, is_private: true,
suggested_partners: data["partners"],
suggested_channels: data["channels"],
suggested_partners: data.partners,
suggested_channels: data.channels,
}); });
});
},
_openComposer: function (options) {
var self = this;
var old_composer = this.composer;
// create the new composer
this.composer = new ChatterComposer(this, this.record.model, options.suggested_partners || [], {
commands_enabled: false,
context: this.context,
input_min_height: 50,
input_max_height: Number.MAX_VALUE,
input_baseline: 14,
is_log: options && options.is_log,
record_name: this.record_name,
default_body: old_composer && old_composer.$input && old_composer.$input.val(),
default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
is_private: options.is_private,
suggested_channels: options.suggested_channels
});
this.composer.on('input_focused', this, function () {
this.composer.mention_set_prefetched_partners(this.mentionSuggestions || []);
});
this.composer.insertAfter(this.$('.o_chatter_topbar')).then(function () {
// destroy existing composer
if (old_composer) {
old_composer.destroy();
}
if (!config.device.touch) {
self.composer.focus();
}
self.composer.on('post_message', self, function (message) {
if (options.is_private) {
self.composer.options.is_log = true;
self.composer.options.is_private = options.is_private;
}
self.fields.thread.postMessage(message).then(function () {
});
},
self._closeComposer(true);
if (self.postRefresh === 'always' || (self.postRefresh === 'recipients' && message.partner_ids.length)) {
self.trigger_up('reload');
_openComposer: function(options) {
var self = this;
var old_composer = this.composer;
// Create the new composer
this.composer = new ChatterComposer(
this,
this.record.model,
options.suggested_partners || [],
{
commands_enabled: false,
context: this.context,
input_min_height: 50,
input_max_height: Number.MAX_VALUE,
input_baseline: 14,
is_log: options && options.is_log,
record_name: this.record_name,
default_body:
old_composer &&
old_composer.$input &&
old_composer.$input.val(),
default_mention_selections:
old_composer && old_composer.mention_get_listener_selections(),
is_private: options.is_private,
suggested_channels: options.suggested_channels,
}
);
this.composer.on("input_focused", this, function() {
this.composer.mention_set_prefetched_partners(
this.mentionSuggestions || []
);
});
this.composer.insertAfter(this.$(".o_chatter_topbar")).then(function() {
// Destroy existing composer
if (old_composer) {
old_composer.destroy();
}
if (!config.device.touch) {
self.composer.focus();
}
self.composer.on("post_message", self, function(message) {
if (options.is_private) {
self.composer.options.is_log = true;
self.composer.options.is_private = options.is_private;
} }
self.fields.thread.postMessage(message).then(function() {
self._closeComposer(true);
if (
self.postRefresh === "always" ||
(self.postRefresh === "recipients" &&
message.partner_ids.length)
) {
self.trigger_up("reload");
}
});
}); });
});
var toggle_post_private = self.composer.options.is_private || false;
self.composer.on('need_refresh', self, self.trigger_up.bind(self, 'reload'));
self.composer.on('close_composer', null, self._closeComposer.bind(self, true));
self.$el.addClass('o_chatter_composer_active');
self.$('.o_chatter_button_new_message, .o_chatter_button_log_note, .oe_compose_post_private').removeClass('o_active');
self.$('.o_chatter_button_new_message').toggleClass('o_active', !self.composer.options.is_log && !self.composer.options.is_private);
self.$('.o_chatter_button_log_note').toggleClass('o_active', (self.composer.options.is_log && !options.is_private));
self.$('.oe_compose_post_private').toggleClass('o_active', toggle_post_private);
});
},
fetch_recipients_for_internal_message: function () {
var self = this;
self.result = {};
var follower_ids_domain = [['id', '=', self.context.default_res_id]];
return rpc.query({
model: 'mail.message',
method: 'send_recepients_for_internal_message',
args: [[], self.context.default_model, follower_ids_domain]
}).then(function (res) {
res["partners"] = _.filter(res["partners"], function (obj) {
return obj.partner_id !== session.partner_id;
});
return res;
});
},
});
var toggle_post_private = self.composer.options.is_private || false;
self.composer.on(
"need_refresh",
self,
self.trigger_up.bind(self, "reload")
);
self.composer.on(
"close_composer",
null,
self._closeComposer.bind(self, true)
);
ChatterComposer.include({
init: function (parent, model, suggested_partners, options) {
this._super(parent, model, suggested_partners, options);
this.suggested_channels = options.suggested_channels;
this.events['click .oe_composer_uncheck'] = 'on_uncheck_recipients';
if (typeof options.is_private === 'undefined') {
// otherwise it causes an error in context creating function
options.is_private = false;
}
},
preprocess_message: function () {
var self = this;
var def = $.Deferred();
this._super().then(function (message) {
message = _.extend(message, {
subtype: 'mail.mt_comment',
message_type: 'comment',
content_subtype: 'html',
context: self.context,
self.$el.addClass("o_chatter_composer_active");
self.$(
".o_chatter_button_new_message, .o_chatter_button_log_note, .oe_compose_post_private"
).removeClass("o_active");
self.$(".o_chatter_button_new_message").toggleClass(
"o_active",
!self.composer.options.is_log && !self.composer.options.is_private
);
self.$(".o_chatter_button_log_note").toggleClass(
"o_active",
self.composer.options.is_log && !options.is_private
);
self.$(".oe_compose_post_private").toggleClass(
"o_active",
toggle_post_private
);
}); });
},
// Subtype
if (self.options.is_log) {
message.subtype = 'mail.mt_note';
}
fetch_recipients_for_internal_message: function() {
var self = this;
self.result = {};
var follower_ids_domain = [["id", "=", self.context.default_res_id]];
return rpc
.query({
model: "mail.message",
method: "send_recepients_for_internal_message",
args: [[], self.context.default_model, follower_ids_domain],
})
.then(function(res) {
res.partners = _.filter(res.partners, function(obj) {
return obj.partner_id !== session.partner_id;
});
return res;
});
},
});
if (self.options.is_private) {
message.is_private = true;
message.channel_ids = self.get_checked_channel_ids();
ChatterComposer.include({
init: function(parent, model, suggested_partners, options) {
this._super(parent, model, suggested_partners, options);
this.suggested_channels = options.suggested_channels;
this.events["click .oe_composer_uncheck"] = "on_uncheck_recipients";
if (typeof options.is_private === "undefined") {
// Otherwise it causes an error in context creating function
options.is_private = false;
} }
},
// Partner_ids
if (!self.options.is_log) {
var checked_suggested_partners = self.get_checked_suggested_partners();
self.check_suggested_partners(checked_suggested_partners).done(function (partner_ids) {
message.partner_ids = (message.partner_ids || []).concat(partner_ids);
// update context
message.context = _.defaults({}, message.context, {
mail_post_autofollow: true,
});
def.resolve(message);
preprocess_message: function() {
var self = this;
var def = $.Deferred();
this._super().then(function(message) {
message = _.extend(message, {
subtype: "mail.mt_comment",
message_type: "comment",
content_subtype: "html",
context: self.context,
}); });
} else {
def.resolve(message);
// Subtype
if (self.options.is_log) {
message.subtype = "mail.mt_note";
}
if (self.options.is_private) {
message.is_private = true;
message.channel_ids = self.get_checked_channel_ids();
}
// Partner_ids
if (!self.options.is_log) {
var checked_suggested_partners = self.get_checked_suggested_partners();
self.check_suggested_partners(checked_suggested_partners).done(
function(partner_ids) {
message.partner_ids = (message.partner_ids || []).concat(
partner_ids
);
// Update context
message.context = _.defaults({}, message.context, {
mail_post_autofollow: true,
});
def.resolve(message);
}
);
} else {
def.resolve(message);
}
});
return def;
},
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);
});
},
on_open_full_composer: function() {
if (!this.do_check_attachment_upload()) {
return false;
} }
});
return def;
},
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);
});
},
on_open_full_composer: function() {
if (!this.do_check_attachment_upload()){
return 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,
is_private: self.options.is_private,
};
if (self.options && self.options.is_private) {
context.default_is_private = self.options.is_private;
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,
is_private: self.options.is_private,
};
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;
});
checked_partners = _.uniq(_.filter(checked_partners, function (obj) {
return obj.reason !== 'Channel';
}));
return checked_partners;
},
get_checked_channel_ids: function () {
var self = this;
var checked_channels = [];
this.$('.o_composer_suggested_channels input:checked').each(function() {
var full_name = $(this).data('fullname');
checked_channels = checked_channels.concat(_.filter(self.suggested_channels, function(item) {
return full_name === item.full_name;
}));
});
checked_channels = _.uniq(_.filter(checked_channels, function (obj) {
return obj.reason === 'Channel';
}));
return _.pluck(checked_channels, 'channel_id');
}
});
if (self.options && self.options.is_private) {
context.default_is_private = self.options.is_private;
}
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;
});
checked_partners = _.uniq(
_.filter(checked_partners, function(obj) {
return obj.reason !== "Channel";
})
);
return checked_partners;
},
get_checked_channel_ids: function() {
var self = this;
var checked_channels = [];
this.$(".o_composer_suggested_channels input:checked").each(function() {
var full_name = $(this).data("fullname");
checked_channels = checked_channels.concat(
_.filter(self.suggested_channels, function(item) {
return full_name === item.full_name;
})
);
});
checked_channels = _.uniq(
_.filter(checked_channels, function(obj) {
return obj.reason === "Channel";
})
);
return _.pluck(checked_channels, "channel_id");
},
});
}); });

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: 10000, timeout: 10000,
}, {
},
{
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_send .o_composer_button_send", trigger: ".o_composer_send .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);
}); });

27
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,16 +9,20 @@
<t t-extend="mail.Chatter.Buttons"> <t t-extend="mail.Chatter.Buttons">
<t t-jquery="button[title='Send a message']" t-operation="after"> <t t-jquery="button[title='Send a message']" t-operation="after">
<button t-if="new_message_btn" class="btn btn-sm btn-link oe_compose_post_private" title="Send a message to specified recipients only">Send internal message</button>
<button
t-if="new_message_btn"
class="btn btn-sm btn-link oe_compose_post_private"
title="Send a message to specified recipients only"
>Send internal message</button>
</t> </t>
</t> </t>
<t t-extend="mail.chatter.ChatComposer"> <t t-extend="mail.chatter.ChatComposer">
<t t-jquery="small[class='o_chatter_composer_info']" t-operation="replace"> <t t-jquery="small[class='o_chatter_composer_info']" t-operation="replace">
<small class="o_chatter_composer_info" t-if="!widget.options.is_private"> <small 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
@ -31,16 +35,21 @@
<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">
<div class="o_checkbox"> <div class="o_checkbox">
<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"/>
<span/>
t-att-data-fullname="channel.full_name"
/>
<span />
</div> </div>
<t t-esc="channel.name"/>
<t t-esc="channel.name" />
</div> </div>
</t> </t>
</t> </t>
<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>
</div> </div>
</t> </t>
</t> </t>

24
mail_private/template.xml

@ -1,19 +1,25 @@
<?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).-->
<odoo> <odoo>
<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">
<link rel="stylesheet" href="/mail_private/static/src/css/mail_private.css"/>
<link
rel="stylesheet"
href="/mail_private/static/src/css/mail_private.css"
/>
<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>
</odoo> </odoo>

20
mail_private/tests/test_js.py

@ -8,7 +8,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
@ -16,16 +15,21 @@ 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()
env = Environment(self.registry.test_cr, self.uid, {}) env = Environment(self.registry.test_cr, self.uid, {})
partners = env['res.partner'].search([])
new_follower = env['res.partner'].search([('name', 'ilike', 'Ja')])
partners = env["res.partner"].search([])
new_follower = env["res.partner"].search([("name", "ilike", "Ja")])
for partner in partners: for partner in partners:
partner.message_subscribe(new_follower.ids, []) partner.message_subscribe(new_follower.ids, [])
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=90)
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=90,
)

22
mail_recovery/__manifest__.py

@ -1,18 +1,16 @@
{ {
'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": "11.0.1.0.0",
"depends": ["mail"],
"data": ["data.xml"],
"installable": False, "installable": False,
} }

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);
}, },
}); });

18
mail_reply/__manifest__.py

@ -2,26 +2,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": "11.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": False, "installable": False,
"auto_install": False, "auto_install": False,

2
mail_reply/doc/index.rst

@ -25,4 +25,4 @@ Usage
* SentBox: https://apps.odoo.com/apps/modules/11.0/mail_sent/ * SentBox: https://apps.odoo.com/apps/modules/11.0/mail_sent/
* Archive: https://apps.odoo.com/apps/modules/11.0/mail_archives/ * Archive: https://apps.odoo.com/apps/modules/11.0/mail_archives/
* All Messages: https://www.odoo.com/apps/modules/11.0/mail_all/
* All Messages: https://www.odoo.com/apps/modules/11.0/mail_all/

76
mail_reply/static/src/js/mail_reply.js

@ -1,51 +1,51 @@
odoo.define('mail_reply.reply', function (require) {
"use strict";
odoo.define("mail_reply.reply", function(require) {
"use strict";
var core = require('web.core');
var chat_manager = require('mail_base.base').chat_manager;
var core = require("web.core");
var chat_manager = require("mail_base.base").chat_manager;
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({
_selectMessage: function (message_id) {
this._super.apply(this, arguments);
var message = 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);
},
ChatAction.include({
_selectMessage: function(message_id) {
this._super.apply(this, arguments);
var message = 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);
},
_onPostMessage: 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';
_onPostMessage: 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;
}
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;
}
chat_manager.post_message(message, options).then(function() {
if (self.selected_message) { if (self.selected_message) {
self._renderSnackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000);
self._renderSnackbar(
"mail.chat.MessageSentSnackbar",
{record_name: self.selected_message.record_name},
5000
);
self._unselectMessage(); self._unselectMessage();
} else { } else {
self.thread.scroll_to(); self.thread.scroll_to();
} }
}); });
}
});
return chat_manager;
},
});
return chat_manager;
}); });

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_icon 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_icon 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_icon o_thread_message_reply" class="fa fa-reply o_thread_icon 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>

1
mail_reply/tests/__init__.py

@ -1,2 +1 @@
from . import test_default from . import test_default

7
mail_reply/tests/test_default.py

@ -4,7 +4,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 = """
@ -35,5 +34,7 @@ class TestUi(odoo.tests.HttpCase):
}, 1000); }, 1000);
""" """
link = '/web#action=%s' % self.ref('mail.mail_channel_action_client_chat')
self.phantom_js(link, code, "odoo.__DEBUG__.services['mail_reply.reply']", login="admin")
link = "/web#action=%s" % self.ref("mail.mail_channel_action_client_chat")
self.phantom_js(
link, code, "odoo.__DEBUG__.services['mail_reply.reply']", login="admin"
)

25
mail_sent/__manifest__.py

@ -2,27 +2,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": "11.0.1.1.0", "version": "11.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,35 +1,39 @@
from odoo import api, models, fields
from odoo 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")

2
mail_sent/static/src/css/sent.css

@ -1,3 +1,3 @@
.o_channel_name.mail_sent i { .o_channel_name.mail_sent i {
margin-right: 4px; margin-right: 4px;
}
}

114
mail_sent/static/src/js/sent.js

@ -1,69 +1,69 @@
odoo.define('mail_sent.sent', function (require) {
"use strict";
odoo.define("mail_sent.sent", function(require) {
"use strict";
var core = require('web.core');
var session = require('web.session');
var chat_manager = require('mail_base.base').chat_manager;
var core = require("web.core");
var session = require("web.session");
var chat_manager = require("mail_base.base").chat_manager;
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;
}
});
// Inherit class and override methods
var chat_manager_super = _.clone(chat_manager);
chat_manager.get_properties = function (msg) {
var properties = chat_manager_super.get_properties.apply(this, arguments);
properties.is_sent = this.property_descr("channel_sent", msg, this);
return properties;
};
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;
},
});
chat_manager.set_channel_flags = function (data, msg) {
chat_manager_super.set_channel_flags.apply(this, arguments);
if (data.sent && data.author_id[0] === session.partner_id) {
msg.is_sent = true;
}
return msg;
};
// Inherit class and override methods
var chat_manager_super = _.clone(chat_manager);
chat_manager.get_properties = function(msg) {
var properties = chat_manager_super.get_properties.apply(this, arguments);
properties.is_sent = this.property_descr("channel_sent", msg, this);
return properties;
};
chat_manager.get_channel_array = function (msg) {
var arr = chat_manager_super.get_channel_array.apply(this, arguments);
return arr.concat('channel_sent');
};
chat_manager.set_channel_flags = function(data, msg) {
chat_manager_super.set_channel_flags.apply(this, arguments);
if (data.sent && data.author_id[0] === session.partner_id) {
msg.is_sent = true;
}
return msg;
};
chat_manager.get_domain = function (channel) {
return (channel.id === "channel_sent")
? [['sent', '=', true],['author_id.user_ids', 'in', [session.uid]]]
: chat_manager_super.get_domain.apply(this, arguments);
};
chat_manager.get_channel_array = function(msg) {
var arr = chat_manager_super.get_channel_array.apply(this, arguments);
return arr.concat("channel_sent");
};
chat_manager.get_domain = function(channel) {
return channel.id === "channel_sent"
? [
["sent", "=", true],
["author_id.user_ids", "in", [session.uid]],
]
: chat_manager_super.get_domain.apply(this, arguments);
};
chat_manager.is_ready.then(function () {
// Add sent channel
chat_manager.add_channel({
id: "channel_sent",
name: _lt("Sent"),
type: "static"
chat_manager.is_ready.then(function() {
// Add sent channel
chat_manager.add_channel({
id: "channel_sent",
name: _lt("Sent"),
type: "static",
});
return $.when();
}); });
return $.when();
});
return chat_manager;
return 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_title_main 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_title_main 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>

1
mail_sent/tests/__init__.py

@ -1,2 +1 @@
from . import test_js from . import test_js

10
mail_sent/tests/test_js.py

@ -4,7 +4,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 = """
@ -13,5 +12,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",
)

14
mail_sent/views/templates.xml

@ -1,12 +1,14 @@
<?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">
<link rel="stylesheet" href="/mail_sent/static/src/css/sent.css"/>
<script src="/mail_sent/static/src/js/sent.js" type="text/javascript"></script>
<link rel="stylesheet" href="/mail_sent/static/src/css/sent.css" />
<script src="/mail_sent/static/src/js/sent.js" type="text/javascript" />
</xpath> </xpath>
</template> </template>
</data> </data>

16
mail_to/__manifest__.py

@ -7,26 +7,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": "11.0.1.1.0", "version": "11.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,17 +1,20 @@
# 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

52
mail_to/static/src/js/mail_to.js

@ -3,38 +3,38 @@
* 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 chat_manager = require('mail_base.base').chat_manager;
var chat_manager = require("mail_base.base").chat_manager;
var make_message_super = chat_manager.make_message;
chat_manager.make_message = function (data) {
var msg = make_message_super.call(this, data);
msg.partner_ids = data.partner_ids;
msg.channel_names = data.channel_names;
msg.recipients = data.partner_ids.concat(data.channel_names);
if (!msg.partner_ids && !msg.channel_names) {
return msg;
}
var make_message_super = chat_manager.make_message;
chat_manager.make_message = function(data) {
var msg = make_message_super.call(this, data);
msg.partner_ids = data.partner_ids;
msg.channel_names = data.channel_names;
msg.recipients = data.partner_ids.concat(data.channel_names);
if (!msg.partner_ids && !msg.channel_names) {
return msg;
}
var more_recipients = '';
// value which define more recipients
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 += '; ';
}
}
var more_recipients = "";
// Value which define more recipients
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 += "; ";
}
} }
}
msg.more_recipients = more_recipients;
return msg;
};
msg.more_recipients = more_recipients;
return msg;
};
return chat_manager; return chat_manager;
}); });

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

@ -7,14 +7,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,
)

23
mailgun/__manifest__.py

@ -8,40 +8,27 @@
"images": ["images/mailgun_main.png"], "images": ["images/mailgun_main.png"],
"version": "11.0.1.1.0", "version": "11.0.1.1.0",
"application": False, "application": False,
"author": "IT-Projects LLC, Ildar Nasyrov", "author": "IT-Projects LLC, Ildar Nasyrov",
"support": "apps@it-projects.info", "support": "apps@it-projects.info",
"website": "https://it-projects.info/team/iledarn", "website": "https://it-projects.info/team/iledarn",
"license": "LGPL-3", "license": "LGPL-3",
"price": 389.00, "price": 389.00,
"currency": "EUR", "currency": "EUR",
"depends": [
"mail",
],
"depends": ["mail"],
"external_dependencies": {"python": [], "bin": []}, "external_dependencies": {"python": [], "bin": []},
"data": [
'data/ir_cron_data.xml',
],
"demo": [
],
"qweb": [
],
"data": ["data/ir_cron_data.xml"],
"demo": [],
"qweb": [],
"post_load": None, "post_load": None,
"pre_init_hook": None, "pre_init_hook": None,
"post_init_hook": None, "post_init_hook": None,
"uninstall_hook": None, "uninstall_hook": None,
"auto_install": False, "auto_install": False,
"installable": True, "installable": True,
"demo_title": "Mailgun", "demo_title": "Mailgun",
"demo_addons": [], "demo_addons": [],
"demo_addons_hidden": [], "demo_addons_hidden": [],
"demo_url": "mailgun", "demo_url": "mailgun",
"demo_summary": "Easy to send outgoing and fetch incoming messages by using Mailgun", "demo_summary": "Easy to send outgoing and fetch incoming messages by using Mailgun",
"demo_images": [
"images/mailgun_main.png",
]
"demo_images": ["images/mailgun_main.png"],
} }

16
mailgun/controllers/main.py

@ -1,16 +1,16 @@
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"

6
mailgun/data/ir_cron_data.xml

@ -1,15 +1,15 @@
<?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">
<field name="name">Mailgun - domain verification request</field> <field name="name">Mailgun - domain verification request</field>
<field name="model_id" ref="model_ir_config_parameter"/>
<field name="model_id" ref="model_ir_config_parameter" />
<field name="interval_number">10</field> <field name="interval_number">10</field>
<field name="interval_type">minutes</field> <field name="interval_type">minutes</field>
<field name="numbercall">10</field> <field name="numbercall">10</field>
<field name="state">code</field> <field name="state">code</field>
<field name="code">model.mailgun_verify()</field> <field name="code">model.mailgun_verify()</field>
<field name="doall" eval="True"/>
<field name="doall" eval="True" />
</record> </record>
</data> </data>
</openerp> </openerp>

20
mailgun/models/ir_config_parameter.py

@ -1,24 +1,28 @@
import logging
import requests import requests
import simplejson import simplejson
from openerp import models, api
from odoo import api, models
import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
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.sudo().get_param('mailgun.verified')
verified = self.sudo().get_param("mailgun.verified")
if verified: if verified:
return return
api_key = self.sudo().get_param('mailgun.apikey')
mail_domain = self.sudo().get_param('mail.catchall.domain')
api_key = self.sudo().get_param("mailgun.apikey")
mail_domain = self.sudo().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.sudo().set_param('mailgun.verified', '1')
if (
res.status_code == 200
and simplejson.loads(res.text)["domain"]["state"] == "active"
):
self.sudo().set_param("mailgun.verified", "1")

19
mailgun/models/mail_thread.py

@ -1,17 +1,22 @@
import requests
import logging
from odoo import models, api
import requests
from odoo import api, models
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"))

22
res_partner_company_messages/__manifest__.py

@ -1,17 +1,15 @@
{ {
'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": "11.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': False
"website": "https://twitter.com/yelizariev",
"images": ["images/child.png", "images/parent.png"],
"depends": ["mail"],
"data": ["views.xml"],
"installable": False,
} }

18
res_partner_company_messages/models.py

@ -1,18 +1,20 @@
from openerp import api
from openerp import models
from odoo 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>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save