Skip to content
Permalink
Browse files

[IMP] mail: fix mailing lists proposals

Task #49261

Purpose
=======

Allow inviting any res.partner on mailing lists channels

Specification
=============

1. Inviting people
  - should be linked to the groups
    (i.e.: if I restrict to people belonging to employees,
    show employees only)
  - should display all available partners if public or not group-based,
    not only users

2. In the channel configuration, tab "Members", the "Recipient" field
   is now mandatory
  • Loading branch information...
awa-odoo committed Mar 13, 2019
1 parent bf69975 commit 64ee867dcb347a1cfa2763799e25b155aae4eeb0
@@ -258,36 +258,84 @@ def get_mention_suggestions(self, search, limit=8):
return [users, partners]

@api.model
def im_search(self, name, limit=20):
def im_search(self, name, limit=20, channel_id=None):
""" Search partner with a name and return its id, name and im_status.
Note : the user must be logged
:param name : the partner name to search
:param limit : the limit of result to return
:param channel_id : the channel ID is used to check
- if it's a 'mailing list' (to return the full list of res.partners)
- if the channel has a group_public_id to filter the result
"""

channel = None
is_mailing_list = False
if channel_id and isinstance(channel_id, int):
channel = self.env['mail.channel'].browse(int(channel_id))
is_mailing_list = channel.email_send

# This method is supposed to be used only in the context of channel creation or
# extension via an invite. As both of these actions require the 'create' access
# right, we check this specific ACL.
if self.env['mail.channel'].check_access_rights('create', raise_exception=False):
name = '%' + name + '%'
excluded_partner_ids = [self.env.user.partner_id.id]
self.env.cr.execute("""
SELECT
U.id as user_id,
P.id as id,
P.name as name,
CASE WHEN B.last_poll IS NULL THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away'
ELSE 'online'
END as im_status
FROM res_users U
JOIN res_partner P ON P.id = U.partner_id
LEFT JOIN bus_presence B ON B.user_id = U.id
WHERE P.name ILIKE %s
AND P.id NOT IN %s
AND U.active = 't'
LIMIT %s
""", ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, name, tuple(excluded_partner_ids), limit))
has_restricted_group = channel and channel.public == "groups" and channel.group_public_id
if is_mailing_list and not has_restricted_group:
self._execute_mailing_list_search_query(name, limit, excluded_partner_ids)
else:
self._execute_regular_channel_search_query(name, limit, excluded_partner_ids, channel)
return self.env.cr.dictfetchall()
else:
return {}

def _execute_mailing_list_search_query(self, name, limit, excluded_partner_ids):
return self.env.cr.execute("""
SELECT
P.id as id,
P.name as name
FROM res_partner P
WHERE P.name ILIKE %s
AND P.id NOT IN %s
AND P.active = 't'
AND (P.company_id = %s OR P.company_id is null)
LIMIT %s
""", (name, tuple(excluded_partner_ids), self.env.user.company_id.id, limit))

def _execute_regular_channel_search_query(self, name, limit, excluded_partner_ids, channel):
query = ["""
SELECT
U.id as user_id,
P.id as id,
P.name as name,
CASE WHEN B.last_poll IS NULL THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away'
ELSE 'online'
END as im_status
FROM res_users U
JOIN res_partner P ON P.id = U.partner_id
LEFT JOIN bus_presence B ON B.user_id = U.id
"""]

has_restricted_group = channel and channel.public == "groups" and channel.group_public_id
if has_restricted_group:
query.append("JOIN res_groups_users_rel GU ON U.id = GU.uid")

query.append("""
WHERE P.name ILIKE %s
AND P.id NOT IN %s
AND U.active = 't'
""")

if has_restricted_group:
query.append("AND GU.gid = %s")

query.append("LIMIT %s")

query_parameters = ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, name, tuple(excluded_partner_ids))
if has_restricted_group:
query_parameters += (channel.group_public_id.id,)
query_parameters += (limit,)

self.env.cr.execute(''.join(query), query_parameters)
@@ -63,14 +63,17 @@ var PartnerInviteDialog = Dialog.extend({
return $('<span>').text(item.text).prepend(status);
},
query: function (query) {
self.call('mail_service', 'searchPartner', query.term, 20)
.then(function (partners) {
query.callback({
results: _.map(partners, function (partner) {
return _.extend(partner, { text: partner.label });
}),
});
self.call('mail_service', 'searchPartner', {
searchVal: query.term,
limit: 20,
channelID: self._channelID
}).then(function (partners) {
query.callback({
results: _.map(partners, function (partner) {
return _.extend(partner, { text: partner.label });
}),
});
});
}
});
return this._super.apply(this, arguments);
@@ -689,7 +692,10 @@ var Discuss = AbstractAction.extend({
autoFocus: true,
source: function (request, response) {
self._lastSearchVal = _.escape(request.term);
self.call('mail_service', 'searchPartner', self._lastSearchVal, 10).then(response);
self.call('mail_service', 'searchPartner', {
searchVal: self._lastSearchVal,
limit: 10
}).then(response);
},
select: function (ev, ui) {
var partnerID = ui.item.id;
@@ -40,6 +40,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
* @param {integer} [params.data.partners_info[i].partner_id]
* @param {integer} [params.data.partners_info[i].fetched_message_id]
* @param {integer} [params.data.partners_info[i].seen_message_id]
* @param {string} [params.data.public] either 'public' or 'private' or 'groups'
* @param {string} params.data.state
* @param {string} [params.data.uuid]
* @param {Object} params.options
@@ -68,6 +69,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
this._groupBasedSubscription = data.group_based_subscription;
this._isModerated = data.moderation;
this._isMyselfModerator = data.is_moderator;
this._isGroups = data.public === 'groups';
this._lastMessageDate = undefined;
this._members = data.members || [];
// Promise that is resolved on fetched members of this channel.
@@ -266,6 +268,14 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
isChannel: function () {
return true;
},
/**
* States whether the channel is limited to a single group for inviting users
*
* @returns {boolean}
*/
isGroupBased: function () {
return this._isGroups;
},
/**
* States whether the channel auto-subscribes some users in a group
*
@@ -394,17 +394,31 @@ var MailManager = AbstractService.extend({
/**
* Search among prefetched partners, using the string 'searchVal'
*
* @param {string} searchVal
* @param {integer} limit max number of found partners in the response
* @returns {Promise<Object[]>} list of found partners (matching
* @param {object} params
* @param {string} params.searchVal
* @param {integer} params.limit max number of found partners in the response
* @param {integer} params.[channelID] used to check if the channel is group based or if the channel is a mass mailing
* - Group based: Only the users from this group are displayed
* - Mass mailing (and not group based): All partners are displayed
* @returns {$.Promise<Object[]>} list of found partners (matching
* 'searchVal')
*/
searchPartner: function (searchVal, limit) {
searchPartner: function (params) {
var self = this;
var partners = this._searchPartnerPrefetch(searchVal, limit);

var partners = [];
var channel = this.getChannel(params.channelID);
if (
params.channelID === undefined ||
(!channel.isGroupBased() && !channel.isMassMailing())
) {
partners = this._searchPartnerPrefetch(params);
}

return new Promise(function (resolve, reject) {
// if mass mailing OR group based, we need to fetch from server
if (!partners.length) {
resolve(self._searchPartnerFetch(searchVal, limit));
resolve(self._searchPartnerFetch(params));
} else {
resolve(partners);
}
@@ -1111,36 +1125,40 @@ var MailManager = AbstractService.extend({
/**
* Extend the research to all users
*
* @private
* @param {string} searchVal
* @param {integer} limit
* @param {object} params
* @param {string} params.searchVal
* @param {integer} params.limit max number of found partners in the response
* @param {integer} params.[channelID] used to check if the channel is group based or if the channel is a mass mailing
* - Group based: Only the users from this group are displayed
* - Mass mailing (and not group based): All partners are displayed
* @returns {Promise<Object[]>} fetched partners matching 'searchVal'
*/
_searchPartnerFetch: function (searchVal, limit) {
_searchPartnerFetch: function (params) {
return this._rpc({
model: 'res.partner',
method: 'im_search',
args: [searchVal, limit || 20],
args: [params.searchVal, params.limit || 20, params.channelID],
}, { shadow: true });
},
/**
* Search among prefetched partners
*
* @private
* @param {string} searchVal
* @param {integer} limit
* @param {object} params
* @param {string} params.searchVal
* @param {integer} params.limit max number of found partners in the response
* @returns {string[]} partner suggestions that match searchVal
* (max limit, exclude session partner)
*/
_searchPartnerPrefetch: function (searchVal, limit) {
_searchPartnerPrefetch: function (params) {
var values = [];
var searchRegexp = new RegExp(_.str.escapeRegExp(utils.unaccent(searchVal)), 'i');
var searchRegexp = new RegExp(_.str.escapeRegExp(utils.unaccent(params.searchVal)), 'i');
_.each(this._mentionPartnerSuggestions, function (partners) {
if (values.length < limit) {
if (values.length < params.limit) {
values = values.concat(_.filter(partners, function (partner) {
return (session.partner_id !== partner.id) &&
searchRegexp.test(partner.name);
})).splice(0, limit);
})).splice(0, params.limit);
}
});
return values;
@@ -240,8 +240,10 @@ var ThreadWindow = AbstractThreadWindow.extend({
.autocomplete({
autoFocus: true,
source: function (request, response) {
self.call('mail_service', 'searchPartner', request.term, 10)
.then(response);
self.call('mail_service', 'searchPartner', {
searchVal: request.term,
limit: 10
}).then(response);
},
select: function (event, ui) {
// remember partner ID so that we can replace this window
@@ -134,7 +134,7 @@
<page string="Members">
<field name="channel_last_seen_partner_ids" mode="tree" context="{'active_test': False}">
<tree string="Members" editable="bottom">
<field name="partner_id"/>
<field name="partner_id" required="1"/>
<field name="partner_email" readonly="1"/>
</tree>
</field>

0 comments on commit 64ee867

Please sign in to comment.
You can’t perform that action at this time.