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

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
---
ENV:
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_addons=odoo.addons
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"
apt:
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:
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:
- 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:
- 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}
- travis_install_nightly

24
mail_all/__manifest__.py

@ -3,34 +3,24 @@
"summary": """Checkout all messages where you have access""",
"category": "Discuss",
# "live_test_url": "",
"images": ['images/1.jpg'],
"images": ["images/1.jpg"],
"version": "11.0.1.0.0",
"application": False,
"author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"license": "LGPL-3",
'price': 40.00,
'currency': 'EUR',
"depends": [
"mail_base"
],
"price": 40.00,
"currency": "EUR",
"depends": ["mail_base"],
"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": [],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": None,
"uninstall_hook": None,
'installable': True,
"installable": True,
"auto_install": False,
}

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
chat_manager.add_channel({
id: "channel_all",
name: _lt("All messages"),
type: "static"
type: "static",
});
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>
<!--Inherit Sidebar and add All messages menu item after Starred -->
<t t-extend="mail.chat.Sidebar">
<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>
</t>
</t>

1
mail_all/tests/__init__.py

@ -1,2 +1 @@
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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_all(self):
# wait till page loaded and then click and wait again
code = """
@ -13,5 +12,7 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000);
}, 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>
<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">
<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>
</template>
</data>

23
mail_archives/__manifest__.py

@ -2,25 +2,16 @@
"name": "Mail archives",
"summary": """Adds menu to find old messages""",
"category": "Discuss",
"images": ['images/1.jpg'],
"images": ["images/1.jpg"],
"version": "11.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"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,
}

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>
<!--Inherit Sidebar and add Archive menu item after Starred -->
<t t-extend="mail.chat.Sidebar">
<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>
</t>
</t>

1
mail_archives/tests/__init__.py

@ -1,2 +1 @@
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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_archives(self):
# wait till page loaded and then click and wait again
code = """
@ -13,5 +12,10 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000);
}, 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>
<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">
<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>
</template>
</data>

18
mail_base/__manifest__.py

@ -8,21 +8,13 @@
"category": "Discuss",
"images": [],
"version": "11.0.1.1.1",
"author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"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-2019 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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):
@ -12,7 +13,7 @@ class MailChatController(BusController):
def _poll(self, dbname, channels, last, options):
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)

20
mail_base/models.py

@ -2,32 +2,34 @@
# Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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):
_inherit = 'mail.message'
_inherit = "mail.message"
@api.multi
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:
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)
class MailComposer(models.TransientModel):
_inherit = 'mail.compose.message'
_inherit = "mail.compose.message"
@api.multi
def send_mail(self, auto_commit=False):
res = super(MailComposer, self).send_mail(auto_commit=auto_commit)
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

1
mail_base/tests/__init__.py

@ -1,2 +1 @@
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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_base(self):
# wait till page loaded
code = """
@ -12,5 +11,7 @@ class TestUi(odoo.tests.HttpCase):
console.log('ok');
}, 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>
<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">
<script src="/mail_base/static/lib/base.js" type="text/javascript"></script>
<script src="/mail_base/static/lib/base.js" type="text/javascript" />
</xpath>
</template>
</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",
"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
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):
_inherit = 'fetchmail.server'
_name = 'fetchmail.server'
_inherit = "fetchmail.server"
_name = "fetchmail.server"
_last_updated = None
@ -19,36 +16,54 @@ class FetchMailServer(models.Model):
if not self._last_updated:
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
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
@api.model
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 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()
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:
res[0].run_time = self._run_time()
class FetchMailImmediately(models.AbstractModel):
_name = 'fetch_mail.imm'
_name = "fetch_mail.imm"
@api.model
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]
if array:
return array[0]
@ -58,8 +73,8 @@ class FetchMailImmediately(models.AbstractModel):
@api.model
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_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) {
"use strict";
instance.mail.Wall.include({
init: function(){
init: function() {
this._super.apply(this, arguments);
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();
};
},
@ -16,40 +15,46 @@ openerp.mail_check_immediately = function(instance, local) {
start: function() {
var _this = this;
this._super();
this.get_last_fetched_time();
this.get_time_loop = setInterval(function(){
this.get_time_loop = setInterval(function() {
_this.get_last_fetched_time();
}, 30000);
},
run_fetchmail_manually: function(){
run_fetchmail_manually: function() {
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;
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);
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>
<t t-name="fetch_mail_immediately.header">
<tr class="oe_header_row">
@ -6,19 +6,23 @@
<div class="oe_view_manager_fetch_mail_imm">
<em>
<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>
</em>
</div>
</td>
<td></td>
<td />
</tr>
</t>
<t t-extend="mail.wall">
<t t-jquery="tr.oe_header_row_top" t-operation="after">
<t t-call="fetch_mail_immediately.header">
<t t-set="colspan" t-value="2"/>
<t t-set="colspan" t-value="2" />
</t>
</t>
</t>

11
mail_check_immediately/views.xml

@ -1,8 +1,15 @@
<openerp>
<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">
<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>
</template>
</data>

4
mail_fix_553/__manifest__.py

@ -2,11 +2,11 @@
"name": "Fix mail error 553",
"version": "0.3",
"author": "IT-Projects LLC, Ivan Yelizariev",
'license': 'LGPL-3',
"license": "LGPL-3",
"category": "Discuss",
"support": "apps@it-projects.info",
"website": "https://yelizariev.github.io",
"depends": ["base", "mail"],
"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>
<data noupdate="1">
<!-- Catchall Email Alias -->

160
mail_fix_553/mail_fix_553.py

@ -1,15 +1,14 @@
import base64
import logging
import re
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__)
@ -17,7 +16,9 @@ _logger = logging.getLogger(__name__)
class MailMail(osv.Model):
_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
""" Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
@ -35,50 +36,83 @@ class MailMail(osv.Model):
"""
# 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 {})
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):
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
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:
model = None
if model:
context['model_name'] = model.name
context["model_name"] = model.name
# load attachment binary data with a separate read(), as prefetching all
# `datas` (binary field) could bloat the browse cache, triggerring
# soft/hard mem limits with temporary data.
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
email_list = []
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:
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 = {}
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 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:
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:
try:
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
# would trigger a rollback *after* actually sending the email.
# To avoid sending twice the same email, provoke the failure earlier
mail.write({'state': 'exception'})
mail.write({"state": "exception"})
mail_sent = False
# 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(
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),
reply_to=mail.reply_to,
attachments=attachments,
message_id=mail.message_id,
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:
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:
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
# mail item -> ignore error to avoid blocking
# delivery to next recipients, if any. If this is
# 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:
raise
if res:
mail.write({'state': 'sent', 'message_id': res})
mail.write({"state": "sent", "message_id": res})
mail_sent = True
# /!\ can't use mail.state here, as mail.refresh() will cause an error
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
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:
# prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
# 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
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 isinstance(e, AssertionError):
# 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
value = '. '.join(e.args)
value = ". ".join(e.args)
raise MailDeliveryException(_("Mail Delivery Failed"), value)
raise

33
mail_move_message/__manifest__.py

@ -6,25 +6,18 @@
# 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",
'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).
from odoo.http import request
from odoo.addons.bus.controllers.main import BusController
@ -14,7 +15,7 @@ class MailChatController(BusController):
def _poll(self, dbname, channels, last, options):
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)

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 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).-->
<odoo>
<record id="mail_relocation_models" model="ir.config_parameter">
<field name="key">mail_relocation_models</field>

515
mail_move_message/mail_move_message_models.py

@ -5,31 +5,33 @@
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
# 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.translate import _
from odoo import exceptions
class Wizard(models.TransientModel):
_name = 'mail_move_message.wizard'
_name = "mail_move_message.wizard"
@api.model
def _model_selection(self):
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:
model_names.append(message.model)
if message.moved_from_model and message.moved_from_model not in model_names:
model_names.append(message.moved_from_model)
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
@api.model
@ -39,70 +41,107 @@ class Wizard(models.TransientModel):
available_models = self._model_selection()
if len(available_models):
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
parts = email_split(email_from.replace(' ', ','))
parts = email_split(email_from.replace(" ", ","))
if parts:
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:
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:
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
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_name_from = fields.Char()
# 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()
move_followers = fields.Boolean(
'Move Followers',
"Move Followers",
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.depends('model_record')
@api.depends("model_record")
def _compute_model_res_id(self):
for rec in self:
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
@api.depends('message_id')
@api.depends("message_id")
@api.multi
def _compute_get_can_move(self):
for r in self:
@ -110,32 +149,43 @@ class Wizard(models.TransientModel):
@api.multi
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]
@api.multi
def get_can_move_one(self):
self.ensure_one()
# 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):
if not self.move_back:
return
self.parent_id = self.message_id.moved_from_parent_id
message = self.message_id
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):
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))
)
@api.onchange('parent_id')
@api.onchange("parent_id")
def on_change_parent_id(self):
if self.parent_id and self.parent_id.model:
self.model = self.parent_id.model
@ -144,24 +194,26 @@ class Wizard(models.TransientModel):
self.model = 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):
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:
fields = self.env[self.model].fields_get(False)
contact_field = False
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
break
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:
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
else:
self.res_id = None
return {'domain': domain}
return {"domain": domain}
@api.multi
def check_access(self):
@ -171,16 +223,18 @@ class Wizard(models.TransientModel):
@api.multi
def check_access_one(self):
self.ensure_one()
operation = 'write'
operation = "write"
if not (self.model and self.res_id):
return True
model_obj = self.env[self.model]
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)
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
def open_moved_by_message_id(self):
@ -188,44 +242,60 @@ class Wizard(models.TransientModel):
for r in self:
message_id = r.message_moved_by_message_id.id
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
def move(self):
for r in self:
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:
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
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.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 {
'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 {
'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
@ -239,8 +309,10 @@ class Wizard(models.TransientModel):
msg_id = self.message_id.id
# 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()
return {}
@ -255,20 +327,34 @@ class Wizard(models.TransientModel):
self.ensure_one()
self.message_id.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):
_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
def _compute_get_all_childs(self, include_myself=True):
@ -282,30 +368,59 @@ class MailMessage(models.Model):
if include_myself:
ids.append(self.id)
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:
ids = ids + new_ids
continue
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
@api.multi
def move_followers(self, model, ids):
fol_obj = self.env['mail.followers']
fol_obj = self.env["mail.followers"]
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:
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
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:
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
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()
if parent_id == self.id:
# if for any reason method is called to move message with parent
@ -313,116 +428,126 @@ class MailMessage(models.Model):
# building message tree
return
if not self.author_id:
self.write({
'author_id': author.id,
})
self.write({"author_id": author.id})
vals = {}
if move_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:
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
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
if self.moved_as_unread and move_back:
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:
r_vals = vals.copy()
if not r.is_moved:
# moved_from_* variables contain not last, but original
# 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:
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:
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.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
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
def name_get(self):
context = self.env.context
if not (context or {}).get('extended_name'):
if not (context or {}).get("extended_name"):
return super(MailMessage, self).name_get()
reads = self.read(['record_name', 'model', 'res_id'])
reads = self.read(["record_name", "model", "res_id"])
res = []
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
@api.multi
def message_format(self):
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:
msg = message_index.get(item.id)
if msg:
msg['is_moved'] = item.is_moved
msg["is_moved"] = item.is_moved
return message_values
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
def get_values(self):
res = super(MailMoveMessageConfiguration, self).get_values()
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(
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
@ -431,38 +556,50 @@ class MailMoveMessageConfiguration(models.TransientModel):
super(MailMoveMessageConfiguration, self).set_values()
config_parameters = self.env["ir.config_parameter"].sudo()
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):
_inherit = 'res.partner'
_inherit = "res.partner"
@api.model
def create(self, 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
email_address = (vals['email'].replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_'))
email_address = (
vals["email"]
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")
)
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:
messages.sudo().write({'author_id': res.id})
messages.sudo().write({"author_id": res.id})
return res
@api.model
def default_get(self, default_fields):
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(
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)

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 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).-->
<odoo>
<template id="assets_backend" name="custom bar assets" inherit_id="web.assets_backend">
<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>
</template>
@ -19,51 +24,76 @@
<field name="arch" type="xml">
<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)]}">
<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 attrs="{'invisible':[('can_move', '=', False)]}" colspan="2">
<label for="model_record"/>
<label for="model_record" />
<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>
<label for="filter_by_partner"/>
<label for="filter_by_partner" />
<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"
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>
<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)]}">
<field name="move_back"/>
<field name="move_back" />
</div>
<label for="move_followers"/>
<label for="move_followers" />
<div>
<field name="move_followers"/>
<field name="move_followers" />
</div>
</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" />
<separator string="Message"/>
<separator string="Message" />
<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>
<div class="openerp mail_move_message">
<div class="oe_mail">
@ -71,18 +101,30 @@
<div class="oe_msg_content">
<div class="oe_msg_body">
<!-- use built-in css for messages -->
<field name="message_body"/>
<field name="message_body" />
</div>
</div>
</div>
</div>
</div>
<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>
</form>
</field>
@ -92,30 +134,35 @@
<record id="view_mail_move_message_config_settings" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit</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">
<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>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead">
<div class="o_setting_left_pane">
</div>
<div class="o_setting_right_pane">
<label for="model_ids"/>
<label for="model_ids" />
<div class="text-muted">
Add models to be used for message relocation
</div>
<field name="model_ids" widget="many2many_tags"/>
<field name="model_ids" widget="many2many_tags" />
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead">
<div class="o_setting_left_pane">
<field name="move_followers"/>
<field name="move_followers" />
</div>
<div class="o_setting_right_pane">
<label for="move_followers"/>
<label for="move_followers" />
</div>
</div>
</div>
@ -128,7 +175,7 @@
<field name="name">Mail Relocation</field>
<field name="type">ir.actions.act_window</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="target">inline</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 {
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 {
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 Pavel Romanchenko
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
# 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";
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;
thread.include({
init: function(){
init: function() {
this._super.apply(this, arguments);
// 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);
};
},
on_move_message: function(message_id){
on_move_message: function(message_id) {
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, {
'on_close': function(){}
on_close: function() {},
});
}
},
});
chatter.include({
@ -61,39 +61,39 @@ odoo.define('mail_move_message.relocate', function (require) {
// For show wizard in the form
if (this.fields.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() {});
}
},
});
var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
var ChatAction = core.action_registry.get("mail.chat.instant_messaging");
ChatAction.include({
start: function() {
var result = this._super.apply(this, arguments);
// 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() {});
}
},
});
// override methods of chat manager
// Override methods of chat manager
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);
// Mark msg as moved after reload
msg.is_moved = data.is_moved || false;
return msg;
};
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);
var self = this;
_.each(notifications, function (notification) {
_.each(notifications, function(notification) {
var model = notification[0][1];
var message_id = notification[1].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.model = notification[1].model;
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
self.add_to_cache(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);
})
chat_manager.bus.trigger('update_message', message);
});
chat_manager.bus.trigger("update_message", message);
}
});
};
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({
_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 field_data = event.data.record.data;
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 relField = new relational_fields.FieldMany2One(wid,
'partner_id',
var relField = new relational_fields.FieldMany2One(
wid,
"partner_id",
self.initialState,
{
mode: 'edit',
viewType: 'form',
});
mode: "edit",
viewType: "form",
}
);
relField.getParent = function() {
// necessary for correct _trigger_up implementation in mixins.js
// Necessary for correct _trigger_up implementation in mixins.js
return self;
};
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 {
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 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# Copyright 2016 Pavel Romanchenko
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).-->
<template>
<t t-extend="mail.ChatThread.Message">
<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>
</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).
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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_create_new_partner_and_move_message(self):
env = Environment(self.registry.test_cr, self.uid, {})
# needed because tests are run before the module is marked as
# installed. In js web will only load qweb coming from modules
# that are returned by the backend in module_boot. Without
# 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()
# updating models, to be able relocate messages to a partner at_install
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 = """
var delayed_button_click = function(delay, button){
@ -49,4 +52,6 @@ class TestUi(odoo.tests.HttpCase):
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")
# 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
if email:
user._force_default('email_multi_website', email)
user._force_default("email_multi_website", email)
def uninstall_hook(cr, registry):
@ -26,15 +26,15 @@ def uninstall_hook(cr, registry):
# remove properties
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
cr.execute("SELECT partner_id,email_multi_website FROM res_users")
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"],
"version": "11.0.1.0.0",
"application": False,
"author": "IT-Projects LLC, Ivan Yelizariev",
"support": "apps@it-projects.info",
"website": "https://it-projects.info/team/yelizariev",
"license": "LGPL-3",
"price": 230.00,
"currency": "EUR",
"depends": [
"ir_config_parameter_multi_company",
"web_website",
"mail",
],
"depends": ["ir_config_parameter_multi_company", "web_website", "mail"],
"external_dependencies": {"python": [], "bin": []},
"data": [
"views/website_views.xml",
],
"demo": [
],
"qweb": [
],
"data": ["views/website_views.xml"],
"demo": [],
"qweb": [],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": "post_init_hook",
"uninstall_hook": "uninstall_hook",
"auto_install": False,
"installable": True,
# "demo_title": "Email Addresses per Website",
# "demo_addons": [
# ],

12
mail_multi_website/models/ir_property.py

@ -1,19 +1,19 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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):
_inherit = 'ir.property'
_inherit = "ir.property"
@api.multi
def write(self, vals):
res = super(IrProperty, self).write(vals)
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:
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>
# 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):
_inherit = 'mail.message'
_inherit = "mail.message"
def _default_mail_server_id(self):
website = self.env.context.get('website_id')
website = self.env.context.get("website_id")
if not website:
return
website = self.env['website'].sudo().browse(website)
website = self.env["website"].sudo().browse(website)
return website.mail_server_id.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>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import models, fields, api, tools, _
from odoo import _, api, fields, models, tools
from odoo.exceptions import UserError
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__)
FIELDS = ['body_html', 'mail_server_id', 'report_template']
FIELDS = ["body_html", "mail_server_id", "report_template"]
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:
_logger.warning("jinja2 not available, templating features will not work!")
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)
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
def generate_email(self, res_ids, fields=None):
@ -37,8 +50,8 @@ class MailTemplate(models.Model):
list_of_dict = res
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
@ -54,67 +67,93 @@ class MailTemplate(models.Model):
# try to load the template
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))
except Exception:
_logger.info("Failed to load template %r", template_txt, exc_info=True)
return multi_mode and results or results[res_ids[0]]
# 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)
for record in records:
res_to_rec[record.id] = record
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
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:
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:
website = self.env.user.backend_website_id
for res_id, record in res_to_rec.items():
record_company = 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_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
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
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:
render_result = template.render(variables)
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":
render_result = u""
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
@ -133,13 +172,10 @@ class MailTemplate(models.Model):
res = super(MailTemplate, self).write(vals)
# TODO: will it work with OCA's partner_firstname module?
if 'name' in vals:
if "name" in vals:
fields_to_update = FIELDS
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:
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>
# 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):
_inherit = 'mail.thread'
_inherit = "mail.thread"
@api.model
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:
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>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import models, fields, api
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
FIELD_NAME = 'email_multi_website'
FIELDS = ['signature']
FIELD_NAME = "email_multi_website"
FIELDS = ["signature"]
ALL_FIELDS = [FIELD_NAME] + FIELDS
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)
# 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)
@api.model
def create(self, vals):
res = super(User, self).create(vals)
# make value company independent
res._force_default(FIELD_NAME, vals.get('email'))
res._force_default(FIELD_NAME, vals.get("email"))
for f in FIELDS:
res._force_default(f, vals.get(f))
return res
@ -34,7 +34,7 @@ class User(models.Model):
def write(self, vals):
res = super(User, self).write(vals)
# 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:
self._update_properties_label(f)
return res

6
mail_multi_website/models/website.py

@ -1,10 +1,12 @@
# Copyright 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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):
_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>
# 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.addons.mail.tests.common import TestMail
from odoo.addons.mail.tests.test_mail_gateway import MAIL_TEMPLATE
@ -11,31 +12,45 @@ class TestFetch(TestMail):
def setUp(self):
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
# 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
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):
""" 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
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>
# 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):
_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_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.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.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
# 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_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):
# 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):
"""Mail values are per website"""
@ -74,26 +82,30 @@ class TestRender(TestMail):
# sending without website
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)
# sending from frontend
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.mail_server_id, self.mail_server_id)
# copy-pasted tests
self.assertEqual(mail.email_to, self.email_template.email_to)
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
self.switch_user_website()
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)
def _test_message_post_with_template(self):
@ -109,6 +121,8 @@ class TestRender(TestMail):
self.env.user.invalidate_cache()
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):
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_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
self.assertTrue(self.email)
self.assertNotEqual(self.original_email, self.email)
@ -23,12 +21,14 @@ class TestSendMail(TransactionCase):
def switch_user_website(self):
# 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):
"""User has email addresses per website"""
@ -37,21 +37,28 @@ class TestSendMail(TransactionCase):
self.env.user.email = self.email
# Check that writing works
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
self.env.user.company_id = self.original_company
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):
"""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()
# update partner's email
partner.email = new_email
@ -59,4 +66,6 @@ class TestSendMail(TransactionCase):
# changing company will automatically update website value to empty value
self.env.user.company_id = self.original_company
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>
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -->
<odoo>
<record id="view_website_multi_mail_form" model="ir.ui.view">
<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">
<xpath expr="//field[@name='default_lang_id']" position="after">
<field name="mail_server_id"/>
<field name="mail_server_id" />
</xpath>
</field>
</record>

12
mail_multi_website/wizard/mail_compose_message.py

@ -1,20 +1,22 @@
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# 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
class MailComposer(models.TransientModel):
_inherit = 'mail.compose.message'
_inherit = "mail.compose.message"
@api.model
def create(self, vals):
"""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:
website = self.env['website'].get_current_website()
website = self.env["website"].get_current_website()
if website:
self = self.with_context(website_id=website.id)
return super(MailComposer, self).create(vals)

21
mail_private/__manifest__.py

@ -10,36 +10,23 @@
"name": """Internal Messaging""",
"summary": """Send private messages to specified recipients, regardless of who are in followers list.""",
"category": "Discuss",
"images": ['images/mail_private_image.png'],
"images": ["images/mail_private_image.png"],
"version": "11.0.1.2.0",
"application": False,
"author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"license": "GPL-3",
"price": 50.00,
"currency": "EUR",
"depends": [
"mail",
"base",
"mail_base"
],
"depends": ["mail", "base", "mail_base"],
"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": [],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": None,
"auto_install": False,
"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 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<odoo>
<record model="ir.ui.view" id="email_compose_message_wizard_form_private">
<field name="name">mail.compose.message.form.private</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">
<data>
@ -17,8 +16,13 @@
<field name="is_private" invisible="1" />
</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>
</xpath>

130
mail_private/models.py

@ -4,22 +4,22 @@
# Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
# 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):
_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):
_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):
result = {'partners': [], 'channels': []}
result = {"partners": [], "channels": []}
default_resource = self.env[model].search(domain)
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]
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:
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
@api.multi
def _notify(self, force_send=False, send_after_commit=True, user_signature=True):
self_sudo = self.sudo()
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:
self._notify_mail_private(force_send, send_after_commit, user_signature)
@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
followers. Delegate notification to partners to send emails and bus notifications
and to channels to broadcast messages on channels """
@ -67,17 +76,25 @@ class MailMessage(models.Model):
channels_sudo = self_sudo.channel_ids
# 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
# update message, with maybe custom valuesz
message_values = {}
if channels_sudo:
message_values['channel_ids'] = [(6, 0, channels_sudo.ids)]
message_values["channel_ids"] = [(6, 0, channels_sudo.ids)]
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:
self.write(message_values)
@ -85,14 +102,23 @@ class MailMessage(models.Model):
# 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.
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:
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)
# Discard cache, because child / parent allow reading and therefore
@ -104,14 +130,30 @@ class MailMessage(models.Model):
class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
_inherit = "mail.thread"
@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;
}
.oe_composer_uncheck{
.oe_composer_uncheck {
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 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
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,
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>
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";
var tour = require("web_tour.tour");
var core = require('web.core');
var core = require("web.core");
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")',
content: _t("Open Partners Form"),
position: 'bottom',
}, {
position: "bottom",
},
{
trigger: "button.oe_compose_post_private",
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",
content: _t("Dummy action"),
}, {
},
{
trigger: "button.oe_composer_uncheck",
extra_trigger: "button.oe_composer_uncheck",
content: _t("Uncheck all Followers"),
timeout: 10000,
}, {
},
{
trigger: "div.o_composer_suggested_partners input:first",
content: _t("Check the first one"),
}, {
},
{
trigger: "textarea.o_composer_text_field:first",
content: _t("Write some email"),
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",
content: _t("Send email"),
}, {
},
{
trigger: ".o_mail_thread .o_thread_message:contains(" + 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);
});

25
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 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2016 manawi <https://github.com/manawi>
@ -9,7 +9,11 @@
<t t-extend="mail.Chatter.Buttons">
<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>
@ -18,7 +22,7 @@
<small class="o_chatter_composer_info" t-if="!widget.options.is_private">
To: Followers of
<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-if="!widget.options.record_name">
this document
@ -31,16 +35,21 @@
<t t-foreach='widget.suggested_channels' t-as='channel'>
<div t-attf-title="Add as channel and follower">
<div class="o_checkbox">
<input type="checkbox"
<input
type="checkbox"
t-att-checked="channel.checked ? 'checked' : undefined"
t-att-data-fullname="channel.full_name"/>
<span/>
t-att-data-fullname="channel.full_name"
/>
<span />
</div>
<t t-esc="channel.name"/>
<t t-esc="channel.name" />
</div>
</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>
</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>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<odoo>
<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">
<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
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
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>
</template>
</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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_private(self):
# needed because tests are run before the module is marked as
# 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.
cr = self.registry.cursor()
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()
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:
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,
"currency": "EUR",
"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,
}

13
mail_recovery/data.xml

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp>
<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">
<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>
</template>
</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({
init: function(){
init: function() {
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) {
var $input = $(event.target);
@ -16,8 +17,8 @@ odoo.define('mail_recovery', function (require) {
on_keyup_textarea: function(event) {
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);
},
});

18
mail_reply/__manifest__.py

@ -2,26 +2,18 @@
"name": """Always show reply button""",
"summary": """Got a message out of a Record? Now you can reply to it too!""",
"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",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"license": "LGPL-3",
"price": 40.00,
"currency": "EUR",
"depends": [
"mail_base",
],
"depends": ["mail_base"],
"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": [],
"installable": False,
"auto_install": False,

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) {
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();
} else {
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>
<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"
t-att-data-message-id="message.id" title="Reply"/>
t-att-data-message-id="message.id"
title="Reply"
/>
</t>
</t>
</template>

15
mail_reply/templates.xml

@ -1,11 +1,16 @@
<?xml version="1.0"?>
<?xml version="1.0" ?>
<openerp>
<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">
<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>
</template>
</data>

1
mail_reply/tests/__init__.py

@ -1,2 +1 @@
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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_all(self):
# wait till page loaded and then click and wait again
code = """
@ -35,5 +34,7 @@ class TestUi(odoo.tests.HttpCase):
}, 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",
"summary": """Quick way to find sent messages""",
"category": "Discuss",
"images": ['images/menu.png'],
"images": ["images/menu.png"],
"version": "11.0.1.1.0",
"author": "IT-Projects LLC, Ivan Yelizariev, Pavel Romanchenko",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"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):
_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):
for r in self:
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
)
r.sent = sent
@api.multi
def message_format(self):
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:
msg = message_index.get(item.id)
if msg:
msg['sent'] = item.sent
msg["sent"] = item.sent
return message_values
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")

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>
<!--Inherit Sidebar and add Sent menu item after Starred -->
<t t-extend="mail.chat.Sidebar">
<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>
</t>
</t>
@ -13,7 +18,8 @@
<t t-jquery="t:last-child" t-operation="after">
<t t-if="options.channel_id==='channel_sent'">
<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>

1
mail_sent/tests/__init__.py

@ -1,2 +1 @@
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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_sent(self):
# wait till page loaded and then click and wait again
code = """
@ -13,5 +12,10 @@ class TestUi(odoo.tests.HttpCase):
setTimeout(function () {console.log('ok');}, 3000);
}, 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>
<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">
<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>
</template>
</data>

16
mail_to/__manifest__.py

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

13
mail_to/models/mail_message.py

@ -1,17 +1,20 @@
# Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
# 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):
_inherit = 'mail.message'
_inherit = "mail.message"
@api.multi
def message_format(self):
messages_values = super(MailMessage, self).message_format()
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

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

@ -3,38 +3,38 @@
* Copyright 2017 Artyom Losev <https://it-projects.info/>
* Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
* 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";
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;
});

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

@ -1,20 +1,20 @@
/* Copyright 2018 Artem Rafailov <https://it-projects.info/team/KolushovAlexandr>
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";
var tour = require("web_tour.tour");
var core = require('web.core');
var core = require("web.core");
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"),
position: 'bottom',
position: "bottom",
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 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
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-foreach="message.partner_ids.length" t-as="i">
<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>
</t>
</t>
<t t-if="message.channel_names">
<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>
</t>
</t>
@ -30,7 +45,9 @@
<t t-if="message.recipients.length &gt; message.more_recipients_value">
<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>
</t>
</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 2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<openerp>
<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">
<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>
</template>
</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.post_install(True)
class TestUi(odoo.tests.HttpCase):
def test_01_mail_to(self):
cr = self.registry.cursor()
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()
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"],
"version": "11.0.1.1.0",
"application": False,
"author": "IT-Projects LLC, Ildar Nasyrov",
"support": "apps@it-projects.info",
"website": "https://it-projects.info/team/iledarn",
"license": "LGPL-3",
"price": 389.00,
"currency": "EUR",
"depends": [
"mail",
],
"depends": ["mail"],
"external_dependencies": {"python": [], "bin": []},
"data": [
'data/ir_cron_data.xml',
],
"demo": [
],
"qweb": [
],
"data": ["data/ir_cron_data.xml"],
"demo": [],
"qweb": [],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": None,
"uninstall_hook": None,
"auto_install": False,
"installable": True,
"demo_title": "Mailgun",
"demo_addons": [],
"demo_addons_hidden": [],
"demo_url": "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.http import request
import re
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):
# 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
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>
<data noupdate="1">
<record id="mailgun_domain_verification" model="ir.cron">
<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_type">minutes</field>
<field name="numbercall">10</field>
<field name="state">code</field>
<field name="code">model.mailgun_verify()</field>
<field name="doall" eval="True"/>
<field name="doall" eval="True" />
</record>
</data>
</openerp>

20
mailgun/models/ir_config_parameter.py

@ -1,24 +1,28 @@
import logging
import requests
import simplejson
from openerp import models, api
from odoo import api, models
import logging
_logger = logging.getLogger(__name__)
class IrConfigParameter(models.Model):
_inherit = ['ir.config_parameter']
_inherit = ["ir.config_parameter"]
@api.model
def mailgun_verify(self):
verified = self.sudo().get_param('mailgun.verified')
verified = self.sudo().get_param("mailgun.verified")
if verified:
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:
url = "https://api.mailgun.net/v3/domains/%s/verify" % mail_domain
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__)
class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
_inherit = "mail.thread"
@api.model
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,
"currency": "EUR",
'category': 'Discuss',
"category": "Discuss",
"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):
_inherit = 'res.partner'
_inherit = "res.partner"
@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)
if fields and 'message_ids' in fields:
if fields and "message_ids" in fields:
for vals in res:
partner = self.browse(vals['id'])
partner = self.browse(vals["id"])
if not partner.is_company:
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

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>
</data></openerp>

16
res_partner_mails_count/__manifest__.py

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

15
res_partner_mails_count/models.py

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

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

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

35
res_partner_mails_count/templates.xml

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

2
res_partner_mails_count/tests/__init__.py

@ -1,3 +1,3 @@
from . import test_mail
# from . import test_phantom

67
res_partner_mails_count/tests/test_mail.py

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

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

Loading…
Cancel
Save