flectra/addons/mail/models/mail_followers.py

148 lines
6.7 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 11:34:37 +01:00
from flectra import api, fields, models
class Followers(models.Model):
""" mail_followers holds the data related to the follow mechanism inside
2018-01-16 11:34:37 +01:00
Flectra. Partners can choose to follow documents (records) of any kind
that inherits from mail.thread. Following documents allow to receive
notifications for new messages. A subscription is characterized by:
:param: res_model: model of the followed objects
:param: res_id: ID of resource (may be 0 for every objects)
"""
_name = 'mail.followers'
_rec_name = 'partner_id'
_log_access = False
_description = 'Document Followers'
# Note. There is no integrity check on model names for performance reasons.
# However, followers of unlinked models are deleted by models themselves
# (see 'ir.model' inheritance).
res_model = fields.Char(
'Related Document Model Name', required=True, index=True)
res_id = fields.Integer(
'Related Document ID', index=True, help='Id of the followed resource')
partner_id = fields.Many2one(
'res.partner', string='Related Partner', ondelete='cascade', index=True)
channel_id = fields.Many2one(
'mail.channel', string='Listener', ondelete='cascade', index=True)
subtype_ids = fields.Many2many(
'mail.message.subtype', string='Subtype',
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.")
@api.model
def _add_follower_command(self, res_model, res_ids, partner_data, channel_data, force=True):
""" Please upate me
:param force: if True, delete existing followers before creating new one
using the subtypes given in the parameters
"""
force_mode = force or (all(partner_data.values()) and all(channel_data.values()))
generic = []
specific = {}
existing = {} # {res_id: follower_ids}
p_exist = {} # {partner_id: res_ids}
c_exist = {} # {channel_id: res_ids}
followers = self.sudo().search([
'&',
'&', ('res_model', '=', res_model), ('res_id', 'in', res_ids),
'|', ('partner_id', 'in', list(partner_data)), ('channel_id', 'in', list(channel_data))])
if force_mode:
followers.unlink()
else:
for follower in followers:
existing.setdefault(follower.res_id, list()).append(follower)
if follower.partner_id:
p_exist.setdefault(follower.partner_id.id, list()).append(follower.res_id)
if follower.channel_id:
c_exist.setdefault(follower.channel_id.id, list()).append(follower.res_id)
default_subtypes, _internal_subtypes, external_subtypes = \
self.env['mail.message.subtype'].default_subtypes(res_model)
if force_mode:
employee_pids = self.env['res.users'].sudo().search([('partner_id', 'in', list(partner_data)), ('share', '=', False)]).mapped('partner_id').ids
for pid, data in partner_data.items():
if not data:
if pid not in employee_pids:
partner_data[pid] = external_subtypes.ids
else:
partner_data[pid] = default_subtypes.ids
for cid, data in channel_data.items():
if not data:
channel_data[cid] = default_subtypes.ids
# create new followers, batch ok
gen_new_pids = [pid for pid in partner_data if pid not in p_exist]
gen_new_cids = [cid for cid in channel_data if cid not in c_exist]
for pid in gen_new_pids:
generic.append([0, 0, {'res_model': res_model, 'partner_id': pid, 'subtype_ids': [(6, 0, partner_data.get(pid) or default_subtypes.ids)]}])
for cid in gen_new_cids:
generic.append([0, 0, {'res_model': res_model, 'channel_id': cid, 'subtype_ids': [(6, 0, channel_data.get(cid) or default_subtypes.ids)]}])
# create new followers, each document at a time because of existing followers to avoid erasing
if not force_mode:
for res_id in res_ids:
command = []
doc_followers = existing.get(res_id, list())
new_pids = set(partner_data) - set([sub.partner_id.id for sub in doc_followers if sub.partner_id]) - set(gen_new_pids)
new_cids = set(channel_data) - set([sub.channel_id.id for sub in doc_followers if sub.channel_id]) - set(gen_new_cids)
# subscribe new followers
for new_pid in new_pids:
command.append((0, 0, {
'res_model': res_model,
'partner_id': new_pid,
'subtype_ids': [(6, 0, partner_data.get(new_pid) or default_subtypes.ids)],
}))
for new_cid in new_cids:
command.append((0, 0, {
'res_model': res_model,
'channel_id': new_cid,
'subtype_ids': [(6, 0, channel_data.get(new_cid) or default_subtypes.ids)],
}))
if command:
specific[res_id] = command
return generic, specific
#
# Modifying followers change access rights to individual documents. As the
# cache may contain accessible/inaccessible data, one has to refresh it.
#
@api.multi
def _invalidate_documents(self):
""" Invalidate the cache of the documents followed by ``self``. """
for record in self:
if record.res_id:
self.env[record.res_model].invalidate_cache(ids=[record.res_id])
@api.model
def create(self, vals):
res = super(Followers, self).create(vals)
res._invalidate_documents()
return res
@api.multi
def write(self, vals):
if 'res_model' in vals or 'res_id' in vals:
self._invalidate_documents()
res = super(Followers, self).write(vals)
self._invalidate_documents()
return res
@api.multi
def unlink(self):
self._invalidate_documents()
return super(Followers, self).unlink()
_sql_constraints = [
('mail_followers_res_partner_res_model_id_uniq', 'unique(res_model,res_id,partner_id)', 'Error, a partner cannot follow twice the same object.'),
('mail_followers_res_channel_res_model_id_uniq', 'unique(res_model,res_id,channel_id)', 'Error, a channel cannot follow twice the same object.'),
('partner_xor_channel', 'CHECK((partner_id IS NULL) != (channel_id IS NULL))', 'Error: A follower must be either a partner or a channel (but not both).')
]