Browse Source

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

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

6
.DINAR/image/README.md

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

1
.DINAR/image/src/addons.yaml

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

2
.eslintrc.yml

@ -133,7 +133,7 @@ rules:
no-unused-labels: error
no-unused-vars:
- error
- args: none
- args: none
no-use-before-define: error
no-useless-call: warn
no-useless-computed-key: warn

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=openerp,requests,simplejson

21
.travis.yml

@ -11,27 +11,28 @@ 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="10.0" TESTS="0" LINT_CHECK="0" UNIT_TEST="0"
- PYLINT_ODOO_JSLINTRC="/home/travis/maintainer-quality-tools/travis/cfg/.jslintrc"
- VERSION="10.0" TESTS="0" LINT_CHECK="0" UNIT_TEST="0"
- PYLINT_ODOO_JSLINTRC="/home/travis/maintainer-quality-tools/travis/cfg/.jslintrc"
matrix:
- LINT_CHECK="1"
- CHECK_TAGS="1"
- TESTS="1" ODOO_REPO="odoo/odoo"
- MAKEPOT="1"
- TESTS="1" ODOO_REPO="OCA/OCB"
- LINT_CHECK="1"
- CHECK_TAGS="1"
- TESTS="1" ODOO_REPO="odoo/odoo"
- MAKEPOT="1"
- TESTS="1" ODOO_REPO="OCA/OCB"
virtualenv:
system_site_packages: true
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,27 +3,19 @@
"name": "Show all messages",
"summary": """Checkout all messages where you have access""",
"category": "Discuss",
"images": ['images/1.jpg'],
"version": "1.0.0",
"images": ["images/1.jpg"],
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko",
"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": [],
'installable': True,
"installable": True,
"auto_install": False,
}

1
mail_all/models/__init__.py

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

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

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

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

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template>
<!--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_channel_item #{(active_channel_id == 'channel_all') ? 'o_active': ''}" data-channel-id="channel_all">
<span class="o_channel_name mail_all"> <i class="fa fa-database"/> All messages </span>
<div
t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_all') ? 'o_active': ''}"
data-channel-id="channel_all"
>
<span class="o_channel_name mail_all"> <i
class="fa fa-database"
/> All messages </span>
</div>
</t>
</t>

7
mail_all/tests/test_js.py

@ -5,7 +5,6 @@ import odoo.tests
@odoo.tests.common.at_install(False)
@odoo.tests.common.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 = """
@ -14,5 +13,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"
)

15
mail_all/views/templates.xml

@ -1,11 +1,16 @@
<?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">
<script src="/mail_all/static/src/js/mail_all.js" type="text/javascript"></script>
<script
src="/mail_all/static/src/js/mail_all.js"
type="text/javascript"
/>
</xpath>
</template>
</data>

1
mail_archives/__init__.py

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

25
mail_archives/__manifest__.py

@ -3,25 +3,16 @@
"name": "Mail archives",
"summary": """Adds menu to find old messages""",
"category": "Discuss",
"images": ['images/1.jpg'],
"version": "1.0.0",
"images": ["images/1.jpg"],
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Pavel Romanchenko",
"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,
}

128
mail_archives/static/src/js/archives.js

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

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

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template>
<!--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 #{(active_channel_id == 'channel_archive') ? 'o_active': ''}" data-channel-id="channel_archive">
<span class="o_channel_name mail_archives"> <i class="fa fa-archive"/> Archive </span>
<div
t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_archive') ? 'o_active': ''}"
data-channel-id="channel_archive"
>
<span class="o_channel_name mail_archives"> <i
class="fa fa-archive"
/> Archive </span>
</div>
</t>
</t>

10
mail_archives/tests/test_js.py

@ -5,7 +5,6 @@ import odoo.tests
@odoo.tests.common.at_install(False)
@odoo.tests.common.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 = """
@ -14,5 +13,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",
)

15
mail_archives/views/templates.xml

@ -1,11 +1,16 @@
<?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">
<script src="/mail_archives/static/src/js/archives.js" type="text/javascript"></script>
<script
src="/mail_archives/static/src/js/archives.js"
type="text/javascript"
/>
</xpath>
</template>
</data>

21
mail_attachment_popup/__manifest__.py

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

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

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

32
mail_attachment_popup/static/src/css/simple.css

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

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

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

28
mail_attachment_popup/views/mail_attachment_popup_template.xml

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

18
mail_base/__manifest__.py

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

1
mail_base/controllers/__init__.py

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

6
mail_base/controllers/main.py

@ -2,8 +2,8 @@
# 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 openerp.http import request
class MailChatController(BusController):
@ -13,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)

18
mail_base/models.py

@ -7,28 +7,30 @@ from openerp 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

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

49
mail_check_immediately/models.py

@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
import datetime
from openerp import api, exceptions, fields, models, tools
from openerp.tools.translate import _
from openerp import tools
from openerp import exceptions
from openerp import models, fields, api
class FetchMailServer(models.Model):
_inherit = 'fetchmail.server'
_name = 'fetchmail.server'
_inherit = "fetchmail.server"
_name = "fetchmail.server"
_last_updated = None
@ -20,36 +17,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]
@ -59,8 +74,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>

6
mail_fix_553/__manifest__.py

@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
{
"name": "Fix mail error 553",
"version": "0.3",
"version": "10.0.1.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 -->

157
mail_fix_553/mail_fix_553.py

@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
# TODO
# pylint: disable=old-api7-method-defined,invalid-commit
import base64
import logging
import re
from email.utils import formataddr
from openerp import tools
from openerp import SUPERUSER_ID
from openerp import SUPERUSER_ID, tools
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import osv
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
@ -18,7 +18,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
@ -36,60 +38,93 @@ 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))
headers.update(safe_eval(mail.headers))
except Exception:
pass
# 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
@ -105,58 +140,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

31
mail_move_message/__manifest__.py

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

46
mail_move_message/controllers/main.py

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

2
mail_move_message/data/mail_move_message_data.xml

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

1
mail_move_message/doc/index.rst

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

451
mail_move_message/mail_move_message_models.py

@ -1,28 +1,31 @@
# -*- coding: utf-8 -*-
from openerp import api
from openerp import fields
from openerp import models
from openerp import api, fields, models
from openerp.tools import email_split
from openerp.tools.translate import _
class Wizard(models.TransientModel):
_name = 'mail_move_message.wizard'
_name = "mail_move_message.wizard"
def _model_selection(self):
selection = []
config_parameters = self.env['ir.config_parameter']
model_names = config_parameters.get_param('mail_relocation_models')
model_names = model_names.split(',') if model_names else []
if 'default_message_id' in self.env.context:
message = self.env['mail.message'].browse(self.env.context['default_message_id'])
config_parameters = self.env["ir.config_parameter"]
model_names = config_parameters.get_param("mail_relocation_models")
model_names = model_names.split(",") if model_names else []
if "default_message_id" in self.env.context:
message = self.env["mail.message"].browse(
self.env.context["default_message_id"]
)
if message.model and message.model not in model_names:
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
@ -31,69 +34,107 @@ class Wizard(models.TransientModel):
res = super(Wizard, self).default_get(fields_list)
model_fields = self.fields_get()
if model_fields['model']['selection']:
res['model'] = model_fields['model']['selection'] and model_fields['model']['selection'][0][0]
if 'message_id' in res:
message = self.env['mail.message'].browse(res['message_id'])
if model_fields["model"]["selection"]:
res["model"] = (
model_fields["model"]["selection"]
and model_fields["model"]["selection"][0][0]
)
if "message_id" in res:
message = self.env["mail.message"].browse(res["message_id"])
email_from = message.email_from
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.get_param('mail_relocation_move_followers')
config_parameters = self.env["ir.config_parameter"]
res["move_followers"] = config_parameters.get_param(
"mail_relocation_move_followers"
)
res['uid'] = self.env.uid
res["uid"] = self.env.uid
return res
message_id = fields.Many2one('mail.message', string='Message')
message_body = fields.Html(related='message_id.body', string='Message to move', readonly=True)
message_from = fields.Char(related='message_id.email_from', string='From', readonly=True)
message_subject = fields.Char(related='message_id.subject', string='Subject', readonly=True)
message_moved_by_message_id = fields.Many2one('mail.message', related='message_id.moved_by_message_id', string='Moved with', readonly=True)
message_moved_by_user_id = fields.Many2one('res.users', related='message_id.moved_by_user_id', string='Moved by', readonly=True)
message_is_moved = fields.Boolean(string='Is Moved', related='message_id.is_moved', readonly=True)
parent_id = fields.Many2one('mail.message', string='Search by name', )
model = fields.Selection(_model_selection, string='Model')
res_id = fields.Integer(string='Record')
can_move = fields.Boolean('Can move', compute='_compute_can_move')
move_back = fields.Boolean('MOVE TO ORIGIN', help='Move message and submessages to original place')
partner_id = fields.Many2one('res.partner', string='Author')
filter_by_partner = fields.Boolean('Filter Records by partner')
message_id = fields.Many2one("mail.message", string="Message")
message_body = fields.Html(
related="message_id.body", string="Message to move", readonly=True
)
message_from = fields.Char(
related="message_id.email_from", string="From", readonly=True
)
message_subject = fields.Char(
related="message_id.subject", string="Subject", readonly=True
)
message_moved_by_message_id = fields.Many2one(
"mail.message",
related="message_id.moved_by_message_id",
string="Moved with",
readonly=True,
)
message_moved_by_user_id = fields.Many2one(
"res.users",
related="message_id.moved_by_user_id",
string="Moved by",
readonly=True,
)
message_is_moved = fields.Boolean(
string="Is Moved", related="message_id.is_moved", readonly=True
)
parent_id = fields.Many2one("mail.message", string="Search by name",)
model = fields.Selection(_model_selection, string="Model")
res_id = fields.Integer(string="Record")
can_move = fields.Boolean("Can move", compute="_compute_can_move")
move_back = fields.Boolean(
"MOVE TO ORIGIN", help="Move message and submessages to original place"
)
partner_id = fields.Many2one("res.partner", string="Author")
filter_by_partner = fields.Boolean("Filter Records by partner")
message_email_from = fields.Char()
message_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(related='message_id.needaction')
message_to_read = fields.Boolean(related="message_id.needaction")
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.depends('message_id')
@api.depends("message_id")
@api.multi
def _compute_can_move(self):
for r in self:
# message was not moved before OR message is a top message of previous move
r.can_move = not r.message_id.moved_by_message_id or r.message_id.moved_by_message_id.id == r.message_id.id
r.can_move = (
not r.message_id.moved_by_message_id
or r.message_id.moved_by_message_id.id == r.message_id.id
)
@api.onchange('move_back')
@api.onchange("move_back")
def on_change_move_back(self):
if not self.move_back:
return
@ -103,14 +144,16 @@ class Wizard(models.TransientModel):
self.model = model
self.res_id = self.message_id.moved_from_res_id
@api.onchange('parent_id', 'res_id', 'model')
@api.onchange("parent_id", "res_id", "model")
def update_move_back(self):
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
@ -119,24 +162,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.iteritems():
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):
@ -146,16 +191,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):
@ -163,42 +210,49 @@ 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:
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_id.move(
r.parent_id.id, r.res_id, r.model, r.move_back, r.move_followers
)
if not (r.model and r.res_id):
r.message_id.needaction = False
return {
'type': 'ir.actions.client',
'name': 'All messages',
'tag': 'reload',
"type": "ir.actions.client",
"name": "All messages",
"tag": "reload",
}
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
@ -212,34 +266,40 @@ 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 {}
@api.model
def create_partner(self, message_id, relation, partner_id, message_name_from, message_email_from):
def create_partner(
self, message_id, relation, partner_id, message_name_from, message_email_from
):
model = self.env[relation]
message = self.env['mail.message'].browse(message_id)
message = self.env["mail.message"].browse(message_id)
if not partner_id and message_name_from:
partner_id = self.env['res.partner'].with_context({'update_message_author': True}).create({
'name': message_name_from,
'email': message_email_from
}).id
context = {'partner_id': partner_id}
partner_id = (
self.env["res.partner"]
.with_context({"update_message_author": True})
.create({"name": message_name_from, "email": message_email_from})
.id
)
context = {"partner_id": partner_id}
if model._rec_name:
context.update({'default_%s' % model._rec_name: message.subject})
context.update({"default_%s" % model._rec_name: message.subject})
fields = model.fields_get()
contact_field = False
for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
if f["type"] == "many2one" and f["relation"] == "res.partner":
contact_field = n
break
if contact_field:
context.update({'default_%s' % contact_field: partner_id})
context.update({"default_%s" % contact_field: partner_id})
return context
@api.multi
@ -252,19 +312,33 @@ 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_all_childs', help='all childs, including subchilds')
_inherit = "mail.message"
is_moved = fields.Boolean("Is moved")
moved_from_res_id = fields.Integer("Related Document ID (Original)")
moved_from_model = fields.Char("Related Document Model (Original)")
moved_from_parent_id = fields.Many2one(
"mail.message", "Parent Message (Original)", ondelete="set null"
)
moved_by_message_id = fields.Many2one(
"mail.message",
"Moved by message",
ondelete="set null",
help="Top message, that initate moving this message",
)
moved_by_user_id = fields.Many2one(
"res.users", "Moved by user", ondelete="set null"
)
all_child_ids = fields.One2many(
"mail.message",
string="All childs",
compute="_compute_all_childs",
help="all childs, including subchilds",
)
@api.multi
def _compute_all_childs(self, include_myself=True):
@ -278,27 +352,32 @@ 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):
for r in self:
r.move_one(parent_id, res_id, model, move_back, move_followers=move_followers)
r.move_one(
parent_id, res_id, model, move_back, move_followers=move_followers
)
@api.multi
def move_one(self, parent_id, res_id, model, move_back, move_followers=False):
@ -311,128 +390,142 @@ class MailMessage(models.Model):
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["is_moved"] = False
vals["moved_by_user_id"] = None
vals["moved_by_message_id"] = None
vals['moved_from_res_id'] = None
vals['moved_from_model'] = None
vals['moved_from_parent_id'] = None
vals["moved_from_res_id"] = None
vals["moved_from_model"] = None
vals["moved_from_parent_id"] = None
else:
vals['parent_id'] = parent_id
vals['res_id'] = res_id
vals['model'] = model
vals["parent_id"] = parent_id
vals["res_id"] = res_id
vals["model"] = model
vals['is_moved'] = True
vals['moved_by_user_id'] = self.env.user.id
vals['moved_by_message_id'] = self.id
vals["is_moved"] = True
vals["moved_by_user_id"] = self.env.user.id
vals["moved_by_message_id"] = self.id
# Update record_name in message
vals['record_name'] = self._get_record_name(vals)
vals["record_name"] = self._get_record_name(vals)
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
r_vals['moved_from_res_id'] = r.res_id
r_vals['moved_from_model'] = r.model
r_vals["moved_from_parent_id"] = r.parent_id.id
r_vals["moved_from_res_id"] = r.res_id
r_vals["moved_from_model"] = r.model
elif move_back:
r_vals['parent_id'] = r.moved_from_parent_id.id
r_vals['res_id'] = r.moved_from_res_id
r_vals['model'] = r.moved_from_model
r_vals["parent_id"] = r.moved_from_parent_id.id
r_vals["res_id"] = r.moved_from_res_id
r_vals["model"] = r.moved_from_model
if move_followers:
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': vals['record_name']
"id": self.id,
"res_id": vals.get("res_id"),
"model": vals.get("model"),
"is_moved": vals["is_moved"],
"record_name": vals["record_name"],
}
self.env['bus.bus'].sendone((self._cr.dbname, 'mail_move_message'), notification)
self.env["bus.bus"].sendone(
(self._cr.dbname, "mail_move_message"), notification
)
@api.multi
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):
_name = 'mail_move_message.config.settings'
_inherit = 'res.config.settings'
_name = "mail_move_message.config.settings"
_inherit = "res.config.settings"
model_ids = fields.Many2many(comodel_name='ir.model', string='Models')
move_followers = fields.Boolean('Move Followers')
model_ids = fields.Many2many(comodel_name="ir.model", string="Models")
move_followers = fields.Boolean("Move Followers")
@api.model
def get_default_move_message_configs(self, fields):
config_parameters = self.env['ir.config_parameter']
model_obj = self.env['ir.model']
model_names = config_parameters.get_param('mail_relocation_models')
config_parameters = self.env["ir.config_parameter"]
model_obj = self.env["ir.model"]
model_names = config_parameters.get_param("mail_relocation_models")
if not model_names:
return {}
model_names = model_names.split(',')
model_ids = model_obj.search([('model', 'in', model_names)])
model_names = model_names.split(",")
model_ids = model_obj.search([("model", "in", model_names)])
return {
'model_ids': [m.id for m in model_ids],
'move_followers': config_parameters.get_param('mail_relocation_move_followers')
"model_ids": [m.id for m in model_ids],
"move_followers": config_parameters.get_param(
"mail_relocation_move_followers"
),
}
@api.multi
def set_move_message_configs(self):
config_parameters = self.env['ir.config_parameter']
model_names = ''
config_parameters = self.env["ir.config_parameter"]
model_names = ""
for record in self:
model_names = ','.join([m.model for m in record.model_ids])
config_parameters.set_param('mail_relocation_models', model_names)
config_parameters.set_param('mail_relocation_move_followers', record.move_followers or '')
model_names = ",".join([m.model for m in record.model_ids])
config_parameters.set_param("mail_relocation_models", model_names)
config_parameters.set_param(
"mail_relocation_move_followers", record.move_followers or ""
)
class ResPartner(models.Model):
_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

170
mail_move_message/mail_move_message_views.xml

@ -1,10 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp><data>
<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>
@ -14,54 +20,98 @@
<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"/>
<label for="model" />
<div>
<field name="model" widget="polymorphic" polymorphic="res_id" class="oe_inline"/>
<field
name="model"
widget="polymorphic"
polymorphic="res_id"
class="oe_inline"
/>
</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)]}"
class="oe_highlight oe_inline ml32"
special="quick_create" field="partner_id" context="{'force_email':True,'default_email':message_email_from,'default_name':message_name_from, 'update_message_author':True}" />
<field name="filter_by_partner" class="oe_inline" />
<field name="partner_id" class="oe_inline" />
<button
string="Create Partner"
attrs="{'invisible':[('partner_id','!=',False)]}"
class="oe_highlight oe_inline ml32"
special="quick_create"
field="partner_id"
context="{'force_email':True,'default_email':message_email_from,'default_name':message_name_from, 'update_message_author':True}"
/>
</div>
<label for="res_id"/>
<label for="res_id" />
<div>
<field name="res_id" context="{'extended_name_with_contact':1}" widget="many2one" attrs="{'readonly': [('model','=',False)]}" class="oe_inline"/>
<button string="Create new record" name="create_record" type="object" class="oe_highlight oe_inline ml32" attrs="{'invisible':['|',('model','=',False)]}" special="quick_create" field="res_id" use_for_mail_move_message="True"/>
<field
name="res_id"
context="{'extended_name_with_contact':1}"
widget="many2one"
attrs="{'readonly': [('model','=',False)]}"
class="oe_inline"
/>
<button
string="Create new record"
name="create_record"
type="object"
class="oe_highlight oe_inline ml32"
attrs="{'invisible':['|',('model','=',False)]}"
special="quick_create"
field="res_id"
use_for_mail_move_message="True"
/>
</div>
<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">
@ -69,18 +119,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>
@ -93,16 +155,26 @@
<field name="arch" type="xml">
<form string="Configure Mail Relocation" class="oe_form_configuration">
<header>
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
<button
string="Apply"
type="object"
name="execute"
class="oe_highlight"
/>
or
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
<button
string="Cancel"
type="object"
name="cancel"
class="oe_link"
/>
</header>
<div name="general">
<separator string="Models"/>
<field name="model_ids" widget="many2many_tags"/>
<separator string="Options"/>
<label for="move_followers"/>
<field name="move_followers"/>
<separator string="Models" />
<field name="model_ids" widget="many2many_tags" />
<separator string="Options" />
<label for="move_followers" />
<field name="move_followers" />
</div>
</form>
</field>
@ -112,13 +184,19 @@
<field name="name">Mail Relocation</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mail_move_message.config.settings</field>
<field name="view_id" ref="view_mail_move_message_config_settings"/>
<field name="view_id" ref="view_mail_move_message_config_settings" />
<field name="view_mode">form</field>
<field name="target">inline</field>
</record>
<!-- Add menu entry in Settings/Email -->
<menuitem name="Mail Relocation" id="menu_mail_move_message" parent="base.menu_email" sequence="99" action="action_mail_move_message_config"/>
<menuitem
name="Mail Relocation"
id="menu_mail_move_message"
parent="base.menu_email"
sequence="99"
action="action_mail_move_message_config"
/>
</data>
</openerp>

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;

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

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

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

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

21
mail_private/__manifest__.py

@ -11,36 +11,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": "10.0.1.1.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>

85
mail_private/models.py

@ -5,16 +5,16 @@
# 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")
def get_internal_users_ids(self):
internal_users_ids = self.env['res.users'].search([('share', '=', False)]).ids
internal_users_ids = self.env["res.users"].search([("share", "=", False)]).ids
return internal_users_ids
@api.multi
@ -25,18 +25,25 @@ class MailComposeMessage(models.TransientModel):
class MailMessage(models.Model):
_inherit = 'mail.message'
_inherit = "mail.message"
@api.multi
def _notify(self, force_send=False, send_after_commit=True, user_signature=True):
self_sudo = self.sudo()
if 'is_private' not in self_sudo._context or not self_sudo._context['is_private']:
super(MailMessage, self)._notify(force_send, send_after_commit, user_signature)
if (
"is_private" not in self_sudo._context
or not self_sudo._context["is_private"]
):
super(MailMessage, self)._notify(
force_send, send_after_commit, user_signature
)
else:
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
):
""" The method was partially copied from Odoo.
In the current method, the way of getting channels for a private message is changed.
"""
@ -46,22 +53,33 @@ class MailMessage(models.Model):
# TDE CHECK: add partners / channels as arguments to be able to notify a message with / without computation ??
self.ensure_one() # tde: not sure, just for testinh, will see
partners = self.env['res.partner'] | self.partner_ids
channels = self.env['mail.channel'] | self.channel_ids
partners = self.env["res.partner"] | self.partner_ids
channels = self.env["mail.channel"] | self.channel_ids
# update message, with maybe custom values
message_values = {
'channel_ids': [(6, 0, channels.ids)],
'needaction_partner_ids': [(6, 0, partners.ids)]
"channel_ids": [(6, 0, channels.ids)],
"needaction_partner_ids": [(6, 0, partners.ids)],
}
if self.model and self.res_id and hasattr(self.env[self.model], 'message_get_message_notify_values'):
if (
self.model
and self.res_id
and hasattr(self.env[self.model], "message_get_message_notify_values")
):
message_values.update(
self.env[self.model].browse(self.res_id).message_get_message_notify_values(self, message_values))
self.env[self.model]
.browse(self.res_id)
.message_get_message_notify_values(self, message_values)
)
self.write(message_values)
# notify partners and channels
partners._notify(self, force_send=force_send, send_after_commit=send_after_commit,
user_signature=user_signature)
partners._notify(
self,
force_send=force_send,
send_after_commit=send_after_commit,
user_signature=user_signature,
)
channels._notify(self)
# Discard cache, because child / parent allow reading and therefore
@ -73,15 +91,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
)

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

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

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

@ -1,48 +1,55 @@
/* Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
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: 22000,
}, {
},
{
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_buttons .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,16 +9,20 @@
<t t-extend="mail.Chatter">
<t t-jquery="button[title='Send a message']" t-operation="after">
<button class="btn btn-sm btn-link oe_compose_post_private" t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox" title="Send a message to specified recipients only">Send internal message</button>
<button
class="btn btn-sm btn-link oe_compose_post_private"
t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox"
title="Send a message to specified recipients only"
>Send internal message</button>
</t>
</t>
<t t-extend="mail.chatter.ChatComposer">
<t t-jquery="span[class='o_chatter_composer_info']" t-operation="replace">
<span 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
@ -29,14 +33,19 @@
<div class="o_composer_suggested_channels">
<t t-foreach='widget.suggested_channels' t-as='channel'>
<div t-attf-title="Add as channel and follower">
<input type="checkbox"
<input
type="checkbox"
t-att-checked="channel.checked ? 'checked' : undefined"
t-att-data-fullname="channel.full_name"/>
<t t-esc="channel.name"/>
t-att-data-fullname="channel.full_name"
/>
<t t-esc="channel.name" />
</div>
</t>
</div>
<button class="btn btn-sm btn-link oe_composer_uncheck" t-if="widget.options.is_private">Uncheck all</button>
<button
class="btn btn-sm btn-link oe_composer_uncheck"
t-if="widget.options.is_private"
>Uncheck all</button>
</t>
</t>

19
mail_private/template.xml

@ -1,19 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--Copyright 2016 x620 <https://github.com/x620>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<openerp>
<data>
<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">
<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>
</data>

16
mail_private/tests/test_js.py

@ -9,7 +9,6 @@ from odoo.api import Environment
@odoo.tests.common.at_install(True)
@odoo.tests.common.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
@ -17,10 +16,15 @@ 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()
self.phantom_js("/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_private_tour', 1000)",
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_private_tour.ready",
login="admin", timeout=70)
self.phantom_js(
"/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_private_tour', 1000)",
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_private_tour.ready",
login="admin",
timeout=70,
)

1
mail_recovery/__init__.py

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

22
mail_recovery/__manifest__.py

@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
{
'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": "10.0.1.0.0",
"depends": ["mail"],
"data": ["data.xml"],
"installable": True,
}

13
mail_recovery/data.xml

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<openerp>
<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);
},
});

1
mail_reply/__init__.py

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

18
mail_reply/__manifest__.py

@ -3,26 +3,18 @@
"name": """Always show reply button""",
"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": "10.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": True,
"auto_install": False,

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

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

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

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

25
mail_sent/__manifest__.py

@ -3,27 +3,16 @@
"name": "Sentbox",
"summary": """Quick way to find sent messages""",
"category": "Discuss",
"images": ['images/menu.png'],
"images": ["images/menu.png"],
"version": "10.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,36 +1,40 @@
# -*- coding: utf-8 -*-
from openerp import api, models, fields
from openerp 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")

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

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

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

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<template>
<!--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_channel_item #{(active_channel_id == 'channel_sent') ? 'o_active': ''}" data-channel-id="channel_sent">
<span class="o_channel_name mail_sent"> <i class="fa fa-send-o"/> Sent </span>
<div
t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_sent') ? 'o_active': ''}"
data-channel-id="channel_sent"
>
<span class="o_channel_name mail_sent"> <i
class="fa fa-send-o"
/> Sent </span>
</div>
</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>

10
mail_sent/tests/test_js.py

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

12
mail_sent/views/templates.xml

@ -1,11 +1,13 @@
<?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">
<script src="/mail_sent/static/src/js/sent.js" type="text/javascript"></script>
<script src="/mail_sent/static/src/js/sent.js" type="text/javascript" />
</xpath>
</template>
</data>

16
mail_to/__manifest__.py

@ -8,26 +8,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": "10.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,18 +1,21 @@
# -*- coding: utf-8 -*-
# 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

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

@ -3,13 +3,13 @@
* 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 base_obj = require('mail_base.base');
var base_obj = require("mail_base.base");
base_obj.MailTools.include({
make_message: function(data){
make_message: function(data) {
var msg = this._super(data);
msg.partner_ids = data.partner_ids;
msg.channel_names = data.channel_names;
@ -18,22 +18,22 @@ odoo.define('mail_to.MailTo', function (require) {
return msg;
}
var more_recipients = '';
// value which define 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 += '; ';
}
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;
}
},
});
});

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

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

18
mailgun/__manifest__.py

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

16
mailgun/controllers/main.py

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
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"

2
mailgun/data/cron.xml

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

11
mailgun/doc/index.rst

@ -10,14 +10,14 @@ Usage
* if you in sandbox domain, add Authorized Recepient
* Copy API Key value into odoo
* Open menu ``Settings / Parameters / System Parameters``
* Create new parameter
* key: ``mailgun.apikey``
* Value: API Key from mailgun (``key-...``)
* click Save
* Copy smtp credentials into odoo
* open ``Settings / Technical / Email / Outgoing Mail Servers``
@ -32,7 +32,7 @@ Usage
* Password: ``...`` (copy ``Default Password`` from mailgun)
* From odoo menu ``Settings / General Settings`` edit Alias Domain
* Put your mailgun domain here. E.g. sandbox123...mailgun.org
* Click 'Apply' button
@ -51,6 +51,3 @@ Usage
* Open ``Discuss`` in odoo
* See your message there
* Reply to the message and check it in your mail client (e.g. gmail.com)

35
mailgun/models.py

@ -1,36 +1,43 @@
# -*- coding: utf-8 -*-
import logging
import requests
import simplejson
from openerp import api, models
from openerp import models, api
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"))
class IrConfigParameter(models.Model):
_inherit = ['ir.config_parameter']
_inherit = ["ir.config_parameter"]
@api.model
def mailgun_verify(self):
verified = self.get_param('mailgun.verified')
verified = self.get_param("mailgun.verified")
if verified:
return
api_key = self.get_param('mailgun.apikey')
mail_domain = self.get_param('mail.catchall.domain')
api_key = self.get_param("mailgun.apikey")
mail_domain = self.get_param("mail.catchall.domain")
if api_key and mail_domain:
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.set_param('mailgun.verified', '1')
if (
res.status_code == 200
and simplejson.loads(res.text)["domain"]["state"] == "active"
):
self.set_param("mailgun.verified", "1")

22
res_partner_company_messages/__manifest__.py

@ -1,18 +1,16 @@
# -*- coding: utf-8 -*-
{
'name': "Aggregate messages from company's contacts",
'version': '1.0.0',
'author': 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
"name": "Aggregate messages from company's contacts",
"vesion": "10.0.1.0.0",
"author": "IT-Projects LLC, Ivan Yelizariev",
"license": "LGPL-3",
"price": 70.00,
"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': True
"website": "https://twitter.com/yelizariev",
"images": ["images/child.png", "images/parent.png"],
"depends": ["mail"],
"data": ["views.xml"],
"installable": True,
}

18
res_partner_company_messages/models.py

@ -1,19 +1,21 @@
# -*- coding: utf-8 -*-
from openerp import api
from openerp import models
from openerp 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

@ -3,27 +3,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": "10.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": True,
"auto_install": False,
}

14
res_partner_mails_count/models.py

@ -1,19 +1,25 @@
# -*- coding: utf-8 -*-
from openerp import models, fields, api
from openerp 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>

1
res_partner_mails_count/tests/__init__.py

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

64
res_partner_mails_count/tests/test_mail.py

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

17
res_partner_mails_count/tests/test_phantom.py

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

2
res_partner_mails_count/views/res_partner_mails_count.xml

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

Loading…
Cancel
Save