Browse Source

Merge pull request #180 from Ommo73/12.0-mail_private-port

⬆️1️⃣2️⃣ mail_private *Port*
pull/193/head
Ramil 5 years ago
committed by GitHub
parent
commit
db9903105a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      mail_multi_website/doc/changelog.rst
  2. 10
      mail_private/README.rst
  3. 1
      mail_private/__init__.py
  4. 33
      mail_private/__manifest__.py
  5. 3
      mail_private/full_composer_wizard.xml
  6. 77
      mail_private/models.py
  7. 137
      mail_private/static/src/js/mail_private.js
  8. 29
      mail_private/static/src/js/test_private.js
  9. 11
      mail_private/static/src/xml/mail_private.xml
  10. 3
      mail_private/template.xml
  11. 1
      mail_private/tests/__init__.py
  12. 14
      mail_private/tests/test_js.py

2
mail_multi_website/doc/changelog.rst

@ -1,7 +1,7 @@
`1.0.1`
-------
- **Fix:** Issue related to module uninstallation
- **Fix:** Issue with module uninstallation
`1.0.0`
-------

10
mail_private/README.rst

@ -25,7 +25,7 @@ Maintainers
To get a guaranteed support
you are kindly requested to purchase the module
at `odoo apps store <https://apps.odoo.com/apps/modules/11.0/mail_private/>`__.
at `odoo apps store <https://apps.odoo.com/apps/modules/12.0/mail_private/>`__.
Thank you for understanding!
@ -34,14 +34,14 @@ Maintainers
Further information
===================
Demo: http://runbot.it-projects.info/demo/mail-addons/11.0
Demo: http://runbot.it-projects.info/demo/mail-addons/12.0
HTML Description: https://apps.odoo.com/apps/modules/11.0/mail_private/
HTML Description: https://apps.odoo.com/apps/modules/12.0/mail_private/
Usage instructions: `<doc/index.rst>`_
Changelog: `<doc/changelog.rst>`_
Notifications on updates: `via Atom <https://github.com/it-projects-llc/mail-addons/commits/11.0/mail_private.atom>`_, `by Email <https://blogtrottr.com/?subscribe=https://github.com/it-projects-llc/mail-addons/commits/11.0/mail_private.atom>`_
Notifications on updates: `via Atom <https://github.com/it-projects-llc/mail-addons/commits/12.0/mail_private.atom>`_, `by Email <https://blogtrottr.com/?subscribe=https://github.com/it-projects-llc/mail-addons/commits/12.0/mail_private.atom>`_
Tested on Odoo 11.0 3d09560ffc779e169ed9488e4e07928204dd234d
Tested on Odoo 12.0 5240bc2303816348837425b88fc7ee3ff7de2336

1
mail_private/__init__.py

@ -1,2 +1,3 @@
# License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).
from . import models

33
mail_private/__manifest__.py

@ -1,37 +1,52 @@
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
# Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
# License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).
{
"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'],
"version": "11.0.1.0.2",
# "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=12.0",
"images": [],
"version": "12.0.1.0.2",
"application": False,
"author": "IT-Projects LLC, Pavel Romanchenko",
"support": "apps@it-projects.info",
"website": "https://it-projects.info",
"license": "GPL-3",
"website": "https://it-projects.info/",
"license": "LGPL-3",
"price": 50.00,
"currency": "EUR",
"depends": [
"mail",
"base",
"mail_base"
"mail"
],
"external_dependencies": {"python": [], "bin": []},
"data": [
'template.xml',
'full_composer_wizard.xml',
],
"demo": [
],
"qweb": [
'static/src/xml/mail_private.xml',
],
"demo": [],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": None,
"uninstall_hook": None,
"auto_install": False,
"installable": False,
"installable": True,
# "demo_title": "{MODULE_NAME}",
# "demo_addons": [
# ],
# "demo_addons_hidden": [
# ],
# "demo_url": "DEMO-URL",
# "demo_summary": "{SHORT_DESCRIPTION_OF_THE_MODULE}",
# "demo_images": [
# "images/MAIN_IMAGE",
# ]
}

3
mail_private/full_composer_wizard.xml

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--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>
<record model="ir.ui.view" id="email_compose_message_wizard_form_private">

77
mail_private/models.py

@ -1,3 +1,8 @@
# Copyright 2016 x620 <https://github.com/x620>
# Copyright 2017 Artyom Losev <https://github.com/ArtyomLosev>
# Copyright 2018 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).
from odoo import models, fields, api
@ -41,68 +46,18 @@ class MailMessage(models.Model):
return result
@api.multi
def _notify(self, force_send=False, send_after_commit=True, user_signature=True):
def _notify(self, record, msg_vals, force_send=False, send_after_commit=True, model_description=False, mail_auto_delete=True):
self_sudo = self.sudo()
if not self_sudo.is_private:
super(MailMessage, self)._notify(force_send, send_after_commit, user_signature)
msg_vals = msg_vals if msg_vals else {}
if 'is_private' not in self_sudo._context or not self_sudo._context['is_private']:
return super(MailMessage, self)._notify(record, msg_vals, force_send, send_after_commit, model_description, mail_auto_delete)
else:
self._notify_mail_private(force_send, send_after_commit, user_signature)
rdata = self._notify_compute_internal_recipients(record, msg_vals)
return self._notify_recipients(rdata, record, msg_vals, force_send, send_after_commit, model_description, mail_auto_delete)
@api.multi
def _notify_mail_private(self, force_send=False, send_after_commit=True, user_signature=True):
""" Compute recipients to notify based on specified recipients and document
followers. Delegate notification to partners to send emails and bus notifications
and to channels to broadcast messages on channels """
group_user = self.env.ref('base.group_user')
# have a sudoed copy to manipulate partners (public can go here with website modules like forum / blog / ... )
self_sudo = self.sudo()
self.ensure_one()
partners_sudo = self_sudo.partner_ids
channels_sudo = self_sudo.channel_ids
if self_sudo.subtype_id and self.model and self.res_id:
followers = self_sudo.env['mail.followers'].search([
('res_model', '=', self.model),
('res_id', '=', self.res_id),
('subtype_ids', 'in', self_sudo.subtype_id.id),
])
if self_sudo.subtype_id.internal:
followers = followers.filtered(lambda fol: fol.channel_id or (fol.partner_id.user_ids and group_user in fol.partner_id.user_ids[0].mapped('groups_id')))
channels_sudo |= followers.mapped('channel_id')
# remove author from notified partners
if not self._context.get('mail_notify_author', False) and self_sudo.author_id:
partners_sudo = partners_sudo - self_sudo.author_id
# update message, with maybe custom valuesz
message_values = {}
if channels_sudo:
message_values['channel_ids'] = [(6, 0, channels_sudo.ids)]
if partners_sudo:
message_values['needaction_partner_ids'] = [(6, 0, partners_sudo.ids)]
if self.model and self.res_id and hasattr(self.env[self.model], 'message_get_message_notify_values'):
message_values.update(self.env[self.model].browse(self.res_id).message_get_message_notify_values(self, message_values))
if message_values:
self.write(message_values)
# notify partners and channels
# those methods are called as SUPERUSER because portal users posting messages
# have no access to partner model. Maybe propagating a real uid could be necessary.
email_channels = channels_sudo.filtered(lambda channel: channel.email_send)
notif_partners = partners_sudo.filtered(lambda partner: 'inbox' in partner.mapped('user_ids.notification_type'))
if email_channels or partners_sudo - notif_partners:
partners_sudo.search([
'|',
('id', 'in', (partners_sudo - notif_partners).ids),
('channel_ids', 'in', email_channels.ids),
('email', '!=', self_sudo.author_id.email or self_sudo.email_from),
])._notify(self, force_send=force_send, send_after_commit=send_after_commit, user_signature=user_signature)
channels_sudo._notify(self)
# Discard cache, because child / parent allow reading and therefore
# change access rights.
if self.parent_id:
self.parent_id.invalidate_cache()
return True
def _notify_compute_internal_recipients(self, record, msg_vals):
recipient_data = super(MailMessage, self)._notify_compute_recipients(record, msg_vals)
pids = [x[1] for x in msg_vals.get('partner_ids')] if 'partner_ids' in msg_vals else self.sudo().partner_ids.ids
recipient_data['partners'] = [i for i in recipient_data['partners'] if i['id'] in pids]
return recipient_data

137
mail_private/static/src/js/mail_private.js

@ -2,19 +2,19 @@
Copyright 2016 manavi <https://github.com/manawi>
Copyright 2017-2018 Artyom Losev <https://github.com/ArtyomLosev>
Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). */
odoo.define('mail_private', function (require) {
'use strict';
var core = require('web.core');
var Chatter = require('mail.Chatter');
var ChatterComposer = require('mail.ChatterComposer');
var chat_manager = require('mail_base.base').chat_manager;
var ChatterComposer = require('mail.composer.Chatter');
var session = require('web.session');
var rpc = require('web.rpc');
var config = require('web.config');
var utils = require('mail.utils');
var mailUtils = require('mail.utils');
Chatter.include({
@ -36,51 +36,55 @@ Chatter.include({
_openComposer: function (options) {
var self = this;
var old_composer = this.composer;
var old_composer = this._composer;
// create the new composer
this.composer = new ChatterComposer(this, this.record.model, options.suggested_partners || [], {
commands_enabled: false,
this._composer = new ChatterComposer(this, this.record.model, options.suggested_partners || [], {
commandsEnabled: false,
context: this.context,
input_min_height: 50,
input_max_height: Number.MAX_VALUE,
input_baseline: 14,
is_log: options && options.is_log,
record_name: this.record_name,
default_body: old_composer && old_composer.$input && old_composer.$input.val(),
default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
inputMinHeight: 50,
isLog: options && options.isLog,
recordName: this.recordName,
defaultBody: old_composer && old_composer.$input && old_composer.$input.val(),
defaultMentionSelections: old_composer && old_composer.getMentionListenerSelections(),
attachmentIds: (old_composer && old_composer.get('attachment_ids')) || [],
is_private: options.is_private
});
this.composer.on('input_focused', this, function () {
this.composer.mention_set_prefetched_partners(this.mentionSuggestions || []);
this._composer.on('input_focused', this, function () {
this._composer.mentionSetPrefetchedPartners(this._mentionSuggestions || []);
});
this.composer.insertAfter(this.$('.o_chatter_topbar')).then(function () {
this._composer.insertAfter(this.$('.o_chatter_topbar')).then(function () {
// destroy existing composer
if (old_composer) {
old_composer.destroy();
}
if (!config.device.touch) {
self.composer.focus();
}
self.composer.on('post_message', self, function (message) {
self._composer.focus();
self._composer.on('post_message', self, function (messageData) {
if (options.is_private) {
self.composer.options.is_log = true;
self._composer.options.isLog = true;
}
self.fields.thread.postMessage(message).then(function () {
self._closeComposer(true);
if (self.postRefresh === 'always' || (self.postRefresh === 'recipients' && message.partner_ids.length)) {
self.trigger_up('reload');
}
self._discardOnReload(messageData).then(function () {
self._disableComposer();
self.fields.thread.postMessage(messageData).then(function () {
self._closeComposer(true);
if (self._reloadAfterPost(messageData)) {
self.trigger_up('reload');
} else if (messageData.attachment_ids.length) {
self._reloadAttachmentBox();
self.trigger_up('reload', {fieldNames: ['message_attachment_count'], keepChanges: true});
}
}).fail(function () {
self._enableComposer();
});
});
});
var toggle_post_private = self.composer.options.is_private || false;
self.composer.on('need_refresh', self, self.trigger_up.bind(self, 'reload'));
self.composer.on('close_composer', null, self._closeComposer.bind(self, true));
var toggle_post_private = self._composer.options.is_private || false;
self._composer.on('need_refresh', self, self.trigger_up.bind(self, 'reload'));
self._composer.on('close_composer', null, self._closeComposer.bind(self, true));
self.$el.addClass('o_chatter_composer_active');
self.$('.o_chatter_button_new_message, .o_chatter_button_log_note, .oe_compose_post_private').removeClass('o_active');
self.$('.o_chatter_button_new_message').toggleClass('o_active', !self.composer.options.is_log && !self.composer.options.is_private);
self.$('.o_chatter_button_log_note').toggleClass('o_active', (self.composer.options.is_log && !options.is_private));
self.$('.o_chatter_button_new_message').toggleClass('o_active', !self._composer.options.isLog && !self._composer.options.is_private);
self.$('.o_chatter_button_log_note').toggleClass('o_active', self._composer.options.isLog && !options.is_private);
self.$('.oe_compose_post_private').toggleClass('o_active', toggle_post_private);
});
},
@ -102,8 +106,8 @@ Chatter.include({
});
ChatterComposer.include({
init: function (parent, model, suggested_partners, options) {
this._super(parent, model, suggested_partners, options);
init: function (parent, model, suggestedPartners, options) {
this._super(parent, model, suggestedPartners, options);
this.events['click .oe_composer_uncheck'] = 'on_uncheck_recipients';
if (typeof options.is_private === 'undefined') {
// otherwise it causes an error in context creating function
@ -111,40 +115,39 @@ ChatterComposer.include({
}
},
preprocess_message: function () {
_preprocessMessage: function () {
var self = this;
var def = $.Deferred();
this._super().then(function (message) {
message = _.extend(message, {
subtype: 'mail.mt_comment',
message_type: 'comment',
content_subtype: 'html',
context: self.context,
context: _.defaults({}, self.context, session.user_context),
});
// Subtype
if (self.options.is_log) {
if (self.options.isLog) {
message.subtype = 'mail.mt_note';
}
if (self.options.is_private) {
message.is_private = true;
message.context.is_private = true;
message.channel_ids = self.get_checked_channel_ids();
}
// Partner_ids
if (!self.options.is_log) {
var checked_suggested_partners = self.get_checked_suggested_partners();
self.check_suggested_partners(checked_suggested_partners).done(function (partner_ids) {
message.partner_ids = (message.partner_ids || []).concat(partner_ids);
if (self.options.isLog) {
def.resolve(message);
} else {
var check_suggested_partners = self._getCheckedSuggestedPartners();
self._checkSuggestedPartners(check_suggested_partners).done(function (partnerIDs) {
message.partner_ids = (message.partner_ids || []).concat(partnerIDs);
// update context
message.context = _.defaults({}, message.context, {
mail_post_autofollow: true,
});
def.resolve(message);
});
} else {
def.resolve(message);
}
});
return def;
@ -156,25 +159,30 @@ ChatterComposer.include({
});
},
on_open_full_composer: function() {
if (!this.do_check_attachment_upload()){
_onOpenFullComposer: function () {
if (!this._doCheckAttachmentUpload()){
return false;
}
var self = this;
var recipient_done = $.Deferred();
if (this.options.is_log) {
recipient_done.resolve([]);
} else {
var checked_suggested_partners = this.get_checked_suggested_partners();
recipient_done = this.check_suggested_partners(checked_suggested_partners);
}
recipient_done.then(function (partner_ids) {
var recipientDoneDef = $.Deferred();
this.trigger_up('discard_record_changes', {
proceed: function () {
if (self.options.isLog) {
recipientDoneDef.resolve([]);
} else {
var checkedSuggestedPartners = self._getCheckedSuggestedPartners();
self._checkSuggestedPartners(checkedSuggestedPartners)
.then(recipientDoneDef.resolve.bind(recipientDoneDef));
}
},
});
recipientDoneDef.then(function (partnerIDs) {
var context = {
default_parent_id: self.id,
default_body: utils.get_text2html(self.$input.val()),
default_body: mailUtils.getTextToHTML(self.$input.val()),
default_attachment_ids: _.pluck(self.get('attachment_ids'), 'id'),
default_partner_ids: partner_ids,
default_is_log: self.options.is_log,
default_partner_ids: partnerIDs,
default_is_log: self.options.isLog,
mail_post_autofollow: true,
is_private: self.options.is_private,
};
@ -187,7 +195,7 @@ ChatterComposer.include({
context.default_model = self.context.default_model;
context.default_res_id = self.context.default_res_id;
}
self.do_action({
var action = {
type: 'ir.actions.act_window',
res_model: 'mail.compose.message',
view_mode: 'form',
@ -195,17 +203,14 @@ ChatterComposer.include({
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});
},
};
self.do_action(action, {
on_close: self.trigger.bind(self, 'need_refresh'),
}).then(self.trigger.bind(self, 'close_composer'));
});
},
get_checked_suggested_partners: function () {
_getCheckedSuggestedPartners: 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)

29
mail_private/static/src/js/test_private.js

@ -1,17 +1,30 @@
/* Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
/* Copyright 2018-2019 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
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_private.tour', function (require) {
"use strict";
odoo.define('mail_private.tour', function(require) {
"use strict";
var tour = require("web_tour.tour");
var core = require('web.core');
var tour = require('web_tour.tour');
var _t = core._t;
var email = 'mail_private test email';
var steps = [{
trigger: '.o_thread_message strong.o_mail_redirect:contains("Agrolait")',
content: _t("Open Partners Form"),
var steps = [tour.STEPS.SHOW_APPS_MENU_ITEM, {
trigger: '.fa.fa-cog.o_mail_channel_settings',
content: _t('Select channel settings'),
position: 'bottom',
}, {
trigger: '.nav-link:contains("Members")',
content: _t('Go to the list of subscribers'),
position: 'bottom',
}, {
trigger: '.o_data_cell:contains("YourCompany, Marc Demo")',
content: _t("Select a user"),
position: "bottom",
}, {
trigger: '.o_form_uri.o_field_widget:contains("YourCompany, Marc Demo")',
content: _t("Go to user page"),
position: "bottom"
}, {
trigger: "button.oe_compose_post_private",
content: _t("Click on Private mail creating button"),
@ -26,7 +39,7 @@ odoo.define('mail_private.tour', function (require) {
content: _t("Uncheck all Followers"),
timeout: 10000,
}, {
trigger: "div.o_composer_suggested_partners input:first",
trigger: "div.o_composer_suggested_partners",
content: _t("Check the first one"),
}, {
trigger: "textarea.o_composer_text_field:first",

11
mail_private/static/src/xml/mail_private.xml

@ -1,13 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Copyright 2016-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).-->
<template>
<t t-extend="mail.Chatter.Buttons">
<t t-extend="mail.chatter.Buttons">
<t t-jquery="button[title='Send a message']" t-operation="after">
<button class="btn btn-sm btn-link oe_compose_post_private" title="Send a message to specified recipients only">Send internal message</button>
<button class="btn btn-link oe_compose_post_private" title="Send a message to specified recipients only">Send internal message</button>
</t>
</t>
<t t-extend="mail.chatter.ChatComposer">
<t t-extend="mail.chatter.Composer">
<t t-jquery="small[class='o_chatter_composer_info']" t-operation="replace">
<small class="o_chatter_composer_info" t-if="!widget.options.is_private">
To: Followers of
@ -20,7 +23,7 @@
</small>
</t>
<t t-jquery="div[class='o_composer_suggested_partners']" t-operation="after">
<button class="btn btn-sm btn-link oe_composer_uncheck" t-if="widget.options.is_private">Uncheck all</button>
<button class="btn btn-link oe_composer_uncheck" t-if="widget.options.is_private">Uncheck all</button>
</t>
</t>

3
mail_private/template.xml

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright 2016 x620 <https://github.com/x620>
Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html).-->
<odoo>
<template
id="assets_backend"

1
mail_private/tests/__init__.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import test_js

14
mail_private/tests/test_js.py

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
# Copyright 2019 Artem Rafailov <https://it-projects.info/team/Ommo73/>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import odoo.tests
from odoo.api import Environment
@odoo.tests.common.at_install(True)
@ -16,15 +15,8 @@ class TestUi(odoo.tests.HttpCase):
# that are returned by the backend in module_boot. Without
# 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'
cr.release()
env = Environment(self.registry.test_cr, self.uid, {})
partners = env['res.partner'].search([])
new_follower = env['res.partner'].search([('name', 'ilike', 'Ja')])
for partner in partners:
partner.message_subscribe(new_follower.ids, [])
self.env['ir.module.module'].search([('name', '=', 'mail_private')], limit=1).state = 'installed'
cr._lock.release()
self.phantom_js("/web",
"odoo.__DEBUG__.services['web_tour.tour'].run('mail_private_tour', 1000)",

Loading…
Cancel
Save