[MERGE] Merged OpenChatter-3-5 followers refactoring. Followers is now a modified many2many field, using reference (res_model, res_id) to access one of the relationship. Added message_followers_ids and message_is_follower fields. Refactored subscribers management, because it is now managed through classic reads and writes in the field.

Web-side: the followers display is now a separated widget. It has been moved out of mail.js, into mail_followers.js. Modified views accordingly, because mail_followers is a widget on message_follower_ids field. This is more clean with other OpenERP aspects.
Misc: updated all views using Chatter in addons, to use mail_followers widget. Cleaned some issues. Also renamed mail.subscription into mail.followers, and propagated that change through the code.

bzr revid: tde@openerp.com-20120816003157-f2820hibc17f0j5q
This commit is contained in:
Thibault Delavallée 2012-08-16 02:31:57 +02:00
commit f4f7de1369
54 changed files with 489 additions and 358 deletions

View File

@ -273,6 +273,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -426,6 +427,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -111,6 +111,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -240,6 +240,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -408,6 +409,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -147,6 +147,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -301,6 +302,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -297,7 +297,6 @@ class account_analytic_account(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_subscribe(cr, uid, [obj.id], [obj.user_id.id], context=context)
self.message_append_note(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
account_analytic_account()

View File

@ -45,6 +45,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -233,6 +233,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -859,13 +859,10 @@ class crm_lead(base_stage, osv.osv):
# OpenChatter methods and notifications
# ----------------------------------------
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add the salesman. """
user_ids = super(crm_lead, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' to the monitored fields """
res = super(crm_lead, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id']
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """

View File

@ -222,6 +222,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -532,6 +533,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -271,7 +271,6 @@ class crm_phonecall(base_state, osv.osv):
def case_open_send_note(self, cr, uid, ids, context=None):
lead_obj = self.pool.get('crm.lead')
for phonecall in self.browse(cr, uid, ids, context=context):
phonecall.message_subscribe([phonecall.user_id.id], context=context)
if phonecall.opportunity_id:
lead = phonecall.opportunity_id
# convert datetime field to a datetime, using server format, then

View File

@ -154,6 +154,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -182,6 +182,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -100,6 +100,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -196,6 +196,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -477,6 +478,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -120,6 +120,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -366,15 +366,11 @@ class hr_holidays(osv.osv):
result[obj.id] = hr_manager_group['users']
return result
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add employee and its manager. """
user_ids = super(hr_holidays, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
if obj.employee_id.parent_id and not obj.employee_id.parent_id.user_id.id in user_ids:
user_ids.append(obj.employee_id.parent_id.user_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' and 'manager' to the monitored fields """
res = super(hr_holidays, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
# TODO: add manager
return res + ['user_id']
def create_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):

View File

@ -137,6 +137,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -183,6 +184,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -461,13 +461,10 @@ class hr_applicant(base_stage, osv.Model):
# OpenChatter methods and notifications
# -------------------------------------------------------
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add responsible user. """
user_ids = super(hr_applicant, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' to the monitored fields """
res = super(hr_applicant, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id']
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """

View File

@ -181,6 +181,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -78,6 +78,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -21,9 +21,9 @@
import mail_alias
import mail_message
import mail_followers
import mail_thread
import mail_group
import mail_subscription
import ir_needaction
import res_partner
import res_users

View File

@ -64,7 +64,7 @@ The main features of the module are:
'wizard/mail_compose_message_view.xml',
'res_config_view.xml',
'mail_message_view.xml',
'mail_subscription_view.xml',
'mail_followers_view.xml',
'mail_thread_view.xml',
'mail_group_view.xml',
'res_partner_view.xml',
@ -102,9 +102,11 @@ The main features of the module are:
'js': [
'static/lib/jquery.expander/jquery.expander.js',
'static/src/js/mail.js',
'static/src/js/mail_followers.js',
],
'qweb': [
'static/src/xml/mail.xml',
'static/src/xml/mail_followers.xml',
],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -22,18 +22,21 @@
from osv import osv
from osv import fields
class mail_subscription(osv.osv):
"""
mail_subscription holds the data related to the follow mechanism inside OpenERP.
class mail_followers(osv.Model):
""" mail_followers holds the data related to the follow mechanism inside
OpenERP. Users 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)
:param: user_id: user_id of the follower
"""
_name = 'mail.subscription'
_name = 'mail.followers'
_rec_name = 'id'
_log_access = False
_order = 'res_model asc'
_description = 'Mail subscription'
_description = 'Mail Document Followers'
_columns = {
'res_model': fields.char('Related Document Model', size=128,
required=True, select=1,
@ -44,10 +47,8 @@ class mail_subscription(osv.osv):
ondelete='cascade', required=True, select=1),
}
class mail_notification(osv.osv):
"""
mail_notification is a relational table modeling messages pushed to users.
:param: read: not used currently
class mail_notification(osv.Model):
""" mail_notification is a relational table modeling messages pushed to users.
"""
_name = 'mail.notification'
_rec_name = 'id'
@ -59,9 +60,4 @@ class mail_notification(osv.osv):
ondelete='cascade', required=True, select=1),
'message_id': fields.many2one('mail.message', string='Message',
ondelete='cascade', required=True, select=1),
'read': fields.boolean('Read', help="Not used currently",),
# TODO: add a timestamp ? or use message date ?
}
_defaults = {
'read': False,
}

View File

@ -2,16 +2,14 @@
<openerp>
<data>
<!--
SUBSCRIPTION
!-->
<record model="ir.ui.view" id="view_subscription_tree">
<field name="name">mail.subscription.tree</field>
<field name="model">mail.subscription</field>
<!-- FOLLOWERS !-->
<record model="ir.ui.view" id="view_followers_tree">
<field name="name">mail.followers.tree</field>
<field name="model">mail.followers</field>
<field name="type">tree</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Subscription">
<tree string="Followers">
<field name="res_model"/>
<field name="res_id"/>
<field name="user_id"/>
@ -19,26 +17,22 @@
</field>
</record>
<!--
NOTIFICATION
!-->
<!-- NOTIFICATION !-->
<record model="ir.ui.view" id="view_notification_tree">
<field name="name">mail.notification.tree</field>
<field name="model">mail.notification</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Subscription">
<tree string="Notifications">
<field name="user_id"/>
<field name="message_id"/>
<field name="read"/>
</tree>
</field>
</record>
<record id="action_view_subscriptions" model="ir.actions.act_window">
<field name="name">Subscriptions</field>
<field name="res_model">mail.subscription</field>
<record id="action_view_followers" model="ir.actions.act_window">
<field name="name">Followers</field>
<field name="res_model">mail.followers</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
@ -50,9 +44,9 @@
<field name="view_mode">tree,form</field>
</record>
<!-- Add subscriptions related menu entries in Settings/Email -->
<menuitem name="Subscriptions" id="menu_email_subscriptions" parent="base.menu_email"
action="action_view_subscriptions" sequence="30" groups="base.group_no_one"/> -->
<!-- Add followers related menu entries in Settings/Email -->
<menuitem name="Followers" id="menu_email_followers" parent="base.menu_email"
action="action_view_followers" sequence="30" groups="base.group_no_one"/> -->
<!-- Add notifications related menu entry in Settings/Email -->
<menuitem name="Notifications" id="menu_email_notifications" parent="base.menu_email"

View File

@ -27,18 +27,12 @@ from osv import osv
from osv import fields
from tools.translate import _
class mail_group(osv.osv):
class mail_group(osv.Model):
"""
A mail_group is a collection of users sharing messages in a discussion
group. Group users are users that follow the mail group, using the
subscription/follow mechanism of OpenSocial. A mail group has nothing
in common with res.users.group.
Additional information on fields:
- ``member_ids``: user member of the groups are calculated with
``message_get_subscribers`` method from mail.thread
- ``member_count``: calculated with member_ids
- ``is_subscriber``: calculated with member_ids
"""
_description = 'Discussion group'
@ -55,26 +49,7 @@ class mail_group(osv.osv):
def _set_image(self, cr, uid, id, name, value, args, context=None):
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
def get_member_ids(self, cr, uid, ids, field_names, args, context=None):
if context is None:
context = {}
result = dict.fromkeys(ids)
for id in ids:
result[id] = {}
result[id]['member_ids'] = self.message_get_subscribers(cr, uid, [id], context=context)
result[id]['member_count'] = len(result[id]['member_ids'])
result[id]['is_subscriber'] = uid in result[id]['member_ids']
return result
def search_member_ids(self, cr, uid, obj, name, args, context=None):
if context is None:
context = {}
sub_obj = self.pool.get('mail.subscription')
sub_ids = sub_obj.search(cr, uid, ['&', ('res_model', '=', obj._name), ('user_id', '=', args[0][2])], context=context)
subs = sub_obj.read(cr, uid, sub_ids, context=context)
return [('id', 'in', map(itemgetter('res_id'), subs))]
def get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None):
def _get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None):
result = {}
for id in ids:
lower_date = (DT.datetime.now() - DT.timedelta(days=30)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
@ -86,21 +61,21 @@ class mail_group(osv.osv):
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
_columns = {
#'name': fields.char('Group Name', size=64, required=True),
'description': fields.text('Description'),
'menu_id': fields.many2one('ir.ui.menu', string='Related Menu', required=True, ondelete="cascade"),
'responsible_id': fields.many2one('res.users', string='Responsible',
ondelete='set null', required=True, select=1,
help="Responsible of the group that has all rights on the record."),
'public': fields.selection([('public','Public'),('private','Private'),('groups','Selected Group Only')], 'Privacy', required=True,
help='This group is visible by non members. \
Invisible groups can add members through the invite button.'),
'public': fields.selection([('public', 'Public'), ('private', 'Private'), ('groups', 'Selected Group Only')],
string='Privacy', required=True,
help='This group is visible by non members. '\
'Invisible groups can add members through the invite button.'),
'group_public_id': fields.many2one('res.groups', string='Authorized Group'),
'group_ids': fields.many2many('res.groups', rel='mail_group_res_group_rel',
id1='mail_group_id', id2='groups_id', string='Auto Subscription',
help="Members of those groups will automatically added as followers. "\
"Note that they will be able to manage their subscription manually "\
"if necessary."),
"Note that they will be able to manage their subscription manually "\
"if necessary."),
'image': fields.binary("Photo",
help="This field holds the image used as photo for the "\
"user. The image is base64 encoded, and PIL-supported. "\
@ -121,15 +96,7 @@ class mail_group(osv.osv):
help="Small-sized photo of the group. It is automatically "\
"resized as a 50x50px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required."),
'member_ids': fields.function(get_member_ids, fnct_search=search_member_ids,
type='many2many', relation='res.users', string='Group members', multi='get_member_ids',
deprecated='This field will be deleted in a few hours or days, so please do not use it.'),
'member_count': fields.function(get_member_ids, type='integer',
string='Member count', multi='get_member_ids',
deprecated='This field will be deleted in a few hours or days, so please do not use it.'),
'is_subscriber': fields.function(get_member_ids, type='boolean',
string='Joined', multi='get_member_ids'),
'last_month_msg_nbr': fields.function(get_last_month_msg_nbr, type='integer',
'last_month_msg_nbr': fields.function(_get_last_month_msg_nbr, type='integer',
string='Messages count for last month'),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade",
help="The email address associated with this group. New emails received will automatically "
@ -218,3 +185,12 @@ class mail_group(osv.osv):
def action_group_leave(self, cr, uid, ids, context=None):
return self.message_unsubscribe(cr, uid, ids, context=context)
# ----------------------------------------
# OpenChatter methods and notifications
# ----------------------------------------
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'responsible_id' to the monitored fields """
res = super(mail_group, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['responsible_id']

View File

@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -24,21 +24,29 @@ from osv import fields
from tools.translate import _
class ir_ui_menu(osv.osv):
""" Override of ir.ui.menu class. When adding mail_thread module, each
new mail.group will create a menu entry. This overrides checks that
the current user is in the mail.group followers. If not, the menu
entry is taken off the list of menu ids. This way the user will see
menu entries for the mail.group he is following.
"""
_inherit = 'ir.ui.menu'
_columns = {
'mail_group_id': fields.many2one('mail.group', 'Mail Group')
}
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False)
subs = self.pool.get('mail.subscription')
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
""" Override to take off menu entries (mail.group) the user is not
following. """
ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False)
follower_obj = self.pool.get('mail.followers')
for menu in self.browse(cr, uid, ids, context=context):
if menu.mail_group_id:
sub_ids = subs.search(cr, uid, [
('user_id','=',uid),('res_model','=','mail.group'),
('res_id','=',menu.mail_group_id.id)
sub_ids = follower_obj.search(cr, uid, [
('user_id', '=', uid), ('res_model', '=', 'mail.group'),
('res_id', '=', menu.mail_group_id.id)
], context=context)
if not sub_ids:
ids.remove(menu.id)
return ids

View File

@ -14,6 +14,8 @@
<field name="priority" eval="10"/>
<field name="arch" type="xml">
<kanban>
<field name="message_follower_ids"/>
<field name="message_is_follower"/>
<templates>
<t t-name="kanban-description">
<div class="oe_group_description" t-if="record.description.raw_value">
@ -21,17 +23,19 @@
</div>
</t>
<t t-name="kanban-box">
<div t-attf-class="{record.is_subscriber.raw_value} oe_group_vignette">
<div t-attf-class="{record.message_is_follower.raw_value} oe_group_vignette">
<div class="oe_group_image">
<a type="edit"><img t-att-src="kanban_image('mail.group', 'image_medium', record.id.value)" class="oe_group_photo" tooltip="kanban-description"/></a>
</div>
<div class="oe_group_details">
<h4><a type="edit"><field name="name"/></a></h4>
<span style="display: none;"><field name="is_subscriber"/></span>
<ul>
<li><field name="member_count"/> members</li>
<li t-if="! record.is_subscriber.raw_value"><a name="action_group_join" string="Join" type="object" class="oe_group_join">Not following</a></li>
<li t-if="record.is_subscriber.raw_value"><a name="action_group_leave" string="Join" type="object" class="oe_group_leave">Following</a></li>
<!-- <li><field name="message_follower_count"/> member(s)</li> -->
<li>
<t t-raw="record.message_follower_ids.raw_value.length"/> member(s)
</li>
<li t-if="! record.message_is_follower.raw_value"><a name="action_group_join" string="Join" type="object" class="oe_group_join">Not following</a></li>
<li t-if="record.message_is_follower.raw_value"><a name="action_group_leave" string="Join" type="object" class="oe_group_leave">Following</a></li>
<li><field name="last_month_msg_nbr"/> messages last month</li>
</ul>
</div>
@ -84,6 +88,8 @@
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"
options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"
context="{'lapin': 'nouille'}" image="image_small"/>
</div>
</form>
</field>

View File

@ -36,10 +36,61 @@ from tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
def decode_header(message, header, separator=' '):
return separator.join(map(decode,message.get_all(header, [])))
class many2many_reference(fields.many2many):
""" many2many_reference is an override of fields.many2many. It manages
many2many-like table where one id is given by two fields, res_model
and res_id.
"""
def _get_query_and_where_params(self, cr, model, ids, values, where_params):
""" Add in where:
- mail_followers.res_model = 'crm.lead'
"""
query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
FROM %(rel)s, %(from_c)s \
WHERE %(rel)s.%(id1)s IN %%s \
AND %(rel)s.%(id2)s = %(tbl)s.id \
AND %(rel)s.res_model = %%s \
%(where_c)s \
%(order_by)s \
%(limit)s \
OFFSET %(offset)d' \
% values
where_params = [model._name] + where_params
return query, where_params
def set(self, cr, model, id, name, values, user=None, context=None):
""" Override to add the res_model field in queries. """
if not values: return
rel, id1, id2 = self._sql_names(model)
obj = model.pool.get(self._obj)
for act in values:
if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
continue
if act[0] == 0:
idnew = obj.create(cr, user, act[2], context=context)
cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, idnew, model._name))
elif act[0] == 3:
cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name))
elif act[0] == 4:
# following queries are in the same transaction - so should be relatively safe
cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name))
if not cr.fetchone():
cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act[1], model._name))
elif act[0] == 6:
d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
if d1:
d1 = ' and ' + ' and '.join(d1)
else:
d1 = ''
cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND res_model=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2)
for act_nbr in act[2]:
cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act_nbr, model._name))
else:
return super(many2many_reference, self).set(cr, model, id, name, values, user, context)
class mail_thread(osv.Model):
'''Mixin model, meant to be inherited by any model that needs to
@ -62,22 +113,24 @@ class mail_thread(osv.Model):
default implementation will work for any model. However it is common
to override at least the ``message_new`` and ``message_update``
methods (calling ``super``) to add model-specific behavior at
creation and update of a thread; and ``message_get_subscribers``
to manage more precisely the social aspect of the thread through
the followers.
creation and update of a thread.
#TODO: UPDATE WITH SUBTYPE / NEW FOLLOW MECHANISM
'''
_name = 'mail.thread'
_description = 'Email Thread'
def _get_message_ids(self, cr, uid, ids, name, args, context=None):
res = {}
def _get_message_data(self, cr, uid, ids, field_names, args, context=None):
res = dict.fromkeys(ids)
for id in ids:
message_ids = self.message_search(cr, uid, [id], context=context)
subscriber_ids = self.message_get_subscribers(cr, uid, [id], context=context)
res[id] = {
'message_ids': message_ids,
'message_summary': "<span><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (len(message_ids), len(subscriber_ids)),
}
res[id] = {'message_ids': self.message_search(cr, uid, [id], context=context)}
for thread in self.browse(cr, uid, ids, context=context):
message_follower_ids = [follower.id for follower in thread.message_follower_ids]
res[thread.id].update({
'message_is_follower': uid in message_follower_ids,
'message_summary': "<span><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" %
(len(res[thread.id]['message_ids']), len(thread.message_follower_ids))
})
return res
def _search_message_ids(self, cr, uid, obj, name, args, context=None):
@ -86,20 +139,27 @@ class mail_thread(osv.Model):
return [('id', 'in', msg_ids)]
_columns = {
'message_ids': fields.function(_get_message_ids,
'message_ids': fields.function(_get_message_data,
fnct_search=_search_message_ids,
type='one2many', obj='mail.message', _fields_id = 'res_id',
string='Temp messages', multi="_get_message_ids",
help="Functional field holding messages related to the current document."),
string='Messages', multi="_get_message_data",
help="Field holding discussion about the current document."),
'message_follower_ids': many2many_reference('res.users',
rel='mail_followers', id1='res_id', id2='user_id', string="Followers",
help="Followers of the document. The followers have full access to " \
"the document details, as well as the conversation."),
'message_is_follower': fields.function(_get_message_data, method=True,
type='boolean', string='I am Follower', multi='_get_message_data',
help='True if the current user is following the current document.'),
'message_state': fields.boolean('Read',
help="When checked, new messages require your attention."),
'message_summary': fields.function(_get_message_ids, method=True,
type='text', string='Summary', multi="_get_message_ids",
'message_summary': fields.function(_get_message_data, method=True,
type='text', string='Summary', multi='_get_message_data',
help="Holds the Chatter summary (number of messages, ...). "\
"This summary is directly in html format in order to "\
"be inserted in kanban views."),
}
_defaults = {
'message_state': True,
}
@ -109,40 +169,93 @@ class mail_thread(osv.Model):
#------------------------------------------------------
def create(self, cr, uid, vals, context=None):
"""Automatically subscribe the creator """
""" Override of create to subscribe :
- the writer
- followers given by the monitored fields
"""
thread_id = super(mail_thread, self).create(cr, uid, vals, context=context)
if thread_id:
self.message_subscribe(cr, uid, [thread_id], [uid], context=context)
followers_command = self.message_get_automatic_followers(cr, uid, thread_id, vals, fetch_missing=False, context=context)
if followers_command:
self.write(cr, uid, [thread_id], {'message_follower_ids': followers_command}, context=context)
return thread_id
def write(self, cr, uid, ids, vals, context=None):
"""Automatically subscribe the writer"""
""" Override of write to subscribe :
- the writer
- followers given by the monitored fields
"""
if isinstance(ids, (int, long)):
ids = [ids]
write_res = super(mail_thread, self).write(cr, uid, ids, vals, context=context);
if write_res:
self.message_subscribe(cr, uid, ids, [uid], context=context)
return write_res;
for id in ids:
# copy original vals because we are going to modify it
specific_vals = dict(vals)
# we modify followers: do not subscribe the uid
if specific_vals.get('message_follower_ids'):
followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, add_uid=False, context=context)
specific_vals['message_follower_ids'] += followers_command
else:
followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, context=context)
specific_vals['message_follower_ids'] = followers_command
write_res = super(mail_thread, self).write(cr, uid, ids, specific_vals, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
"""Override unlink, to automatically delete
- subscriptions
- messages
"""Override unlink, to automatically delete messages
that are linked with res_model and res_id, not through
a foreign key with a 'cascade' ondelete attribute.
Notifications will be deleted with messages
"""
subscr_obj = self.pool.get('mail.subscription')
msg_obj = self.pool.get('mail.message')
# delete subscriptions
subscr_to_del_ids = subscr_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
subscr_obj.unlink(cr, uid, subscr_to_del_ids, context=context)
# delete messages and notifications
msg_to_del_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
msg_obj.unlink(cr, uid, msg_to_del_ids, context=context)
return super(mail_thread, self).unlink(cr, uid, ids, context=context)
def message_get_automatic_followers(self, cr, uid, id, record_vals, add_uid=True, fetch_missing=False, context=None):
""" Return the command for the many2many follower_ids field to manage
subscribers. Behavior :
- get the monitored fields (ex: ['user_id', 'responsible_id']); those
fields should be relationships to res.users (#TODO: res.partner)
- if this field is in the record_vals: it means it has been modified
thus add its value to the followers
- if this fields is not in record_vals, but fetch_missing paramter
is set to True: fetch the value in the record (use: at creation
for default values, not present in record_vals)
- if add_uid: add the current user (for example: writer is subscriber)
- generate the command and return it
This method has to be used on 1 id, because otherwise it would imply
to track which user.id is used for which record.id.
:param record_vals: values given to the create method of the new
record, or values updated in a write.
:param monitored_fields: a list of fields that are monitored. Those
fields must be many2one fields to the res.users model.
:param fetch_missing: is set to True, the method will read the
record to find values that are not present in record_vals.
#TODO : UPDATE WHEN MERGING TO PARTNERS
"""
# get monitored fields
monitored_fields = self.message_get_monitored_follower_fields(cr, uid, [id], context=context)
modified_fields = [field for field in monitored_fields if field in record_vals.iterkeys()]
other_fields = [field for field in monitored_fields if field not in record_vals.iterkeys()] if fetch_missing else []
# for each monitored field: if in record_vals, it has been modified/added
follower_ids = []
for field in modified_fields:
# do not add 'False'
if record_vals.get(fields):
follower_ids.append(record_vals.get(field))
# for other fields: read in record if fetch_missing (otherwise list is void)
for field in other_fields:
record = self.browse(cr, uid, id, context=context)
value = getattr(record, field)
if value:
follower_ids.append(value)
# add uid if asked and not already present
if add_uid and uid not in follower_ids:
follower_ids.append(uid)
return self.message_subscribe_get_command(cr, uid, follower_ids, context=context)
#------------------------------------------------------
# mail.message wrappers and tools
#------------------------------------------------------
@ -151,21 +264,21 @@ class mail_thread(osv.Model):
""" OpenChatter: wrapper of mail.message create method
- creates the mail.message
- automatically subscribe the message writer
- push the message to subscribed users
- push the message to followers
"""
if context is None:
context = {}
message_obj = self.pool.get('mail.message')
notification_obj = self.pool.get('mail.notification')
# create message
msg_id = self.pool.get('mail.message').create(cr, uid, vals, context=context)
# automatically subscribe the writer of the message
if vals.get('user_id'):
self.message_subscribe(cr, uid, [thread_id], [vals['user_id']], context=context)
# create message
msg_id = message_obj.create(cr, uid, vals, context=context)
record = self.browse(cr, uid, thread_id, context=context)
follower_ids = [follower.id for follower in record.message_follower_ids]
if vals.get('user_id') not in follower_ids:
self.message_subscribe(cr, uid, [thread_id], [vals.get('user_id')], context=context)
# Set as unread if writer is not the document responsible
self.message_create_set_unread(cr, uid, [thread_id], context=context)
@ -174,6 +287,7 @@ class mail_thread(osv.Model):
return msg_id
# get users that will get a notification pushed
notification_obj = self.pool.get('mail.notification')
user_to_push_ids = self.message_get_user_ids_to_notify(cr, uid, [thread_id], vals, context=context)
for id in user_to_push_ids:
notification_obj.create(cr, uid, {'user_id': id, 'message_id': msg_id}, context=context)
@ -188,11 +302,10 @@ class mail_thread(osv.Model):
body = new_msg_vals.get('body_html', '') if new_msg_vals.get('content_subtype') == 'html' else new_msg_vals.get('body_text', '')
# get subscribers
notif_user_ids = self.message_get_subscribers(cr, uid, thread_ids, context=context)
# add users requested via parsing message (@login)
notif_user_ids += self.message_parse_users(cr, uid, body, context=context)
subscr_obj = self.pool.get('mail.followers')
subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', thread_ids)], context=context)
notif_user_ids = [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)]
# add users requested to perform an action (need_action mechanism)
if hasattr(self, 'get_needaction_user_ids') and self._columns.get('user_id'):
user_ids_dict = self.get_needaction_user_ids(cr, uid, thread_ids, context=context)
@ -210,17 +323,6 @@ class mail_thread(osv.Model):
notif_user_ids = list(set(notif_user_ids))
return notif_user_ids
def message_parse_users(self, cr, uid, string, context=None):
"""Parse message content
- if find @login -(^|\s)@((\w|@|\.)*)-: returns the related ids
this supports login that are emails (such as @raoul@grobedon.net)
"""
regex = re.compile('(^|\s)@((\w|@|\.)*)')
login_lst = [item[1] for item in regex.findall(string)]
if not login_lst: return []
user_ids = self.pool.get('res.users').search(cr, uid, [('login', 'in', login_lst)], context=context)
return user_ids
#------------------------------------------------------
# Generic message api
#------------------------------------------------------
@ -903,70 +1005,41 @@ class mail_thread(osv.Model):
# Subscription mechanism
#------------------------------------------------------
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Returns the current document followers. Basically this method
checks in mail.subscription for entries with matching res_model,
res_id.
This method can be overriden to add implicit subscribers, such
as project managers, by adding their user_id to the list of
ids returned by this method.
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Returns a list of fields containing a res.user.id. Those fields
will be checked to automatically subscribe those users.
"""
subscr_obj = self.pool.get('mail.subscription')
subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
return [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)]
def message_read_subscribers(self, cr, uid, ids, fields=['id', 'name', 'image_small'], context=None):
""" Returns the current document followers as a read result. Used
mainly for Chatter having only one method to call to have
details about users.
"""
user_ids = self.message_get_subscribers(cr, uid, ids, context=context)
return self.pool.get('res.users').read(cr, uid, user_ids, fields=fields, context=context)
def message_is_subscriber(self, cr, uid, ids, user_id = None, context=None):
""" Check if uid or user_id (if set) is a subscriber to the current
document.
:param user_id: if set, check is done on user_id; if not set
check is done on uid
"""
sub_user_id = uid if user_id is None else user_id
if sub_user_id in self.message_get_subscribers(cr, uid, ids, context=context):
return True
return False
return []
def message_subscribe(self, cr, uid, ids, user_ids = None, context=None):
""" Subscribe the user (or user_ids) to the current document.
:param user_ids: a list of user_ids; if not set, subscribe
uid instead
:param return: new value of followers, for Chatter
"""
subscription_obj = self.pool.get('mail.subscription')
to_subscribe_uids = [uid] if user_ids is None else user_ids
create_ids = []
for id in ids:
already_subscribed_user_ids = self.message_get_subscribers(cr, uid, [id], context=context)
for user_id in to_subscribe_uids:
if user_id in already_subscribed_user_ids: continue
create_ids.append(subscription_obj.create(cr, uid, {'res_model': self._name, 'res_id': id, 'user_id': user_id}, context=context))
return create_ids
write_res = self.write(cr, uid, ids, {'message_follower_ids': self.message_subscribe_get_command(cr, uid, to_subscribe_uids, context)}, context=context)
return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
def message_subscribe_get_command(self, cr, uid, follower_ids, context=None):
""" Generate the many2many command to add followers. """
return [(4, id) for id in follower_ids]
def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None):
""" Unsubscribe the user (or user_ids) from the current document.
:param user_ids: a list of user_ids; if not set, subscribe
uid instead
:param return: new value of followers, for Chatter
"""
# Trying to unsubscribe somebody not in subscribers: returns False
# if special management is needed; allows to know that an automatically
# subscribed user tries to unsubscribe and allows to warn him
to_unsubscribe_uids = [uid] if user_ids is None else user_ids
subscription_obj = self.pool.get('mail.subscription')
to_delete_sub_ids = subscription_obj.search(cr, uid,
['&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('user_id', 'in', to_unsubscribe_uids)], context=context)
if not to_delete_sub_ids:
return False
return subscription_obj.unlink(cr, uid, to_delete_sub_ids, context=context)
write_res = self.write(cr, uid, ids, {'message_follower_ids': self.message_unsubscribe_get_command(cr, uid, to_unsubscribe_uids, context)}, context=context)
return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
def message_unsubscribe_get_command(self, cr, uid, follower_ids, context=None):
""" Generate the many2many command to remove followers. """
return [(3, id) for id in follower_ids]
#------------------------------------------------------
# Notification API
@ -997,15 +1070,12 @@ class mail_thread(osv.Model):
# remove message writer
if user_to_notify_ids.count(new_msg_values.get('user_id')) > 0:
user_to_notify_ids.remove(new_msg_values.get('user_id'))
# get user_ids directly asked
user_to_push_from_parse_ids = self.message_parse_users(cr, uid, body, context=context)
# try to find an email_to
email_to = ''
for user in res_users_obj.browse(cr, uid, user_to_notify_ids, context=context):
if not user.notification_email_pref == 'all' and \
not (user.notification_email_pref == 'to_me' and user.id in user_to_push_from_parse_ids):
# TO BE REFACTORED BY FP, JUSTE REMOVED TO_ME, NOT SURE WHAT S NEW BEHAVIOR
if not user.notification_email_pref == 'all':
continue
if not user.email:
continue

View File

@ -11,6 +11,7 @@
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"
options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</xpath>
</field>

View File

@ -159,10 +159,6 @@ class res_users(osv.Model):
for user in self.browse(cr, uid, ids, context=context):
return user.partner_id.message_read(fetch_ancestors, ancestor_ids, limit, offset, domain)
def message_read_subscribers(self, cr, uid, ids, fields=['id', 'name', 'image_small'], context=None):
for user in self.browse(cr, uid, ids, context=context):
return user.partner_id.message_read_subscribers(fields)
def message_search(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None,
limit=100, offset=0, domain=None, count=False, context=None):
for user in self.browse(cr, uid, ids, context=context):

View File

@ -2,7 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
access_mail_thread,mail.thread,model_mail_thread,base.group_user,1,1,1,0
access_mail_subscription_all,mail.subscription.all,model_mail_subscription,,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,1,1,1
access_mail_notification_all,mail.notification.all,model_mail_notification,,1,1,1,1
access_mail_group,mail.group,model_mail_group,base.group_user,1,1,1,1
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_message_all mail.message.all model_mail_message 1 0 0 0
3 access_mail_message_group_user mail.message.group.user model_mail_message base.group_user 1 1 1 1
4 access_mail_thread mail.thread model_mail_thread base.group_user 1 1 1 0
5 access_mail_subscription_all access_mail_followers_all mail.subscription.all mail.followers.all model_mail_subscription model_mail_followers 1 1 1 1
6 access_mail_notification_all mail.notification.all model_mail_notification 1 1 1 1
7 access_mail_group mail.group model_mail_group base.group_user 1 1 1 1
8 access_mail_alias_user mail.alias model_mail_alias base.group_user 1 1 1 0

View File

@ -21,7 +21,7 @@
<field name="name">Mail.group: access only public and joined groups</field>
<field name="model_id" ref="model_mail_group"/>
<!-- This rule has to be improved for employee only groups -->
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('member_ids', 'in', [user.id]), '&amp;', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]</field>
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&amp;', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]</field>
</record>
</data>

View File

@ -89,10 +89,14 @@
/* RecordThread
/* ------------------------------------------------------------ */
.openerp div.oe_mail_recthread {
.openerp .oe_form div.oe_chatter {
overflow: auto;
}
.openerp div.oe_mail_recthread {
/*overflow: auto;*/
}
.openerp div.oe_mail_recthread_main {
float: left;
width: 560px;

View File

@ -4,6 +4,8 @@ openerp.mail = function(session) {
var mail = session.mail = {};
openerp_mail_followers(session, mail); // import mail_followers.js
/**
* ------------------------------------------------------------
* FormView
@ -838,32 +840,17 @@ openerp.mail = function(session) {
this._super.apply(this, arguments);
this.params = this.get_definition_options();
this.params.thread_level = this.params.thread_level || 0;
this.params.see_subscribers = true;
this.params.see_subscribers_options = this.params.see_subscribers_options || false;
this.thread = null;
this.ds = new session.web.DataSet(this, this.view.model);
this.ds_users = new session.web.DataSet(this, 'res.users');
},
start: function() {
var self = this;
// NB: all the widget should be modified to check the actual_mode property on view, not use
// any other method to know if the view is in create mode anymore
this.view.on("change:actual_mode", this, this._check_visibility);
this._check_visibility();
mail.ChatterUtils.bind_events(this);
this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); });
if (! this.params.see_subscribers_options) {
this.$element.find('button.oe_mail_button_followers').hide(); }
this.$element.find('button.oe_mail_button_follow').click(function () { self.do_follow(); })
.mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
.mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
.mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
.mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
this.reinit();
},
_check_visibility: function() {
@ -874,72 +861,20 @@ openerp.mail = function(session) {
this._super.apply(this, arguments);
},
reinit: function() {
this.params.see_subscribers = true;
this.params.see_subscribers_options = this.params.see_subscribers_options || false;
this.$element.find('button.oe_mail_button_followers').html('Hide followers')
this.$element.find('button.oe_mail_button_follow').hide();
this.$element.find('button.oe_mail_button_unfollow').hide();
},
set_value: function() {
this._super.apply(this, arguments);
var self = this;
this.reinit();
if (! this.view.datarecord.id ||
session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
this.$element.find('.oe_mail_thread').hide();
return;
}
// fetch followers
var fetch_sub_done = this.fetch_subscribers();
// create and render Thread widget
this.$element.find('div.oe_mail_recthread_main').empty();
if (this.thread) this.thread.destroy();
this.thread = new mail.Thread(this, {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'uid': this.session.uid,
'thread_level': this.params.thread_level, 'show_post_comment': true, 'limit': 15});
var thread_done = this.thread.appendTo(this.$element.find('div.oe_mail_recthread_main'));
return fetch_sub_done && thread_done;
},
fetch_subscribers: function () {
return this.ds.call('message_read_subscribers', [[this.view.datarecord.id]]).then(this.proxy('display_subscribers'));
},
display_subscribers: function (records) {
var self = this;
this.is_subscriber = false;
var user_list = this.$element.find('ul.oe_mail_followers_display').empty();
this.$element.find('div.oe_mail_recthread_followers h4').html('Followers (' + records.length + ')');
_(records).each(function (record) {
if (record.id == self.session.uid) { self.is_subscriber = true; }
record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.users', 'image_small', record.id);
$(session.web.qweb.render('mail.record_thread.subscriber', {'record': record})).appendTo(user_list);
});
if (self.is_subscriber) {
self.$element.find('button.oe_mail_button_follow').hide();
self.$element.find('button.oe_mail_button_unfollow').show(); }
else {
self.$element.find('button.oe_mail_button_follow').show();
self.$element.find('button.oe_mail_button_unfollow').hide(); }
},
do_follow: function () {
return this.ds.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('fetch_subscribers'));
},
do_unfollow: function () {
var self = this;
return this.ds.call('message_unsubscribe', [[this.view.datarecord.id]]).then(function (record) {
if (record == false) self.do_notify("Impossible to unsubscribe", "You are automatically subscribed to this record. You cannot unsubscribe.");
}).pipe(this.proxy('fetch_subscribers'));
},
do_toggle_followers: function () {
this.params.see_subscribers = ! this.params.see_subscribers;
if (this.params.see_subscribers) { this.$element.find('button.oe_mail_button_followers').html('Hide followers'); }
else { this.$element.find('button.oe_mail_button_followers').html('Show followers'); }
this.$element.find('div.oe_mail_recthread_followers').toggle();
return thread_done;
},
});
@ -976,7 +911,6 @@ openerp.mail = function(session) {
this.params.res_id = params.res_id || false;
this.params.search_view_id = params.search_view_id || false;
this.params.thread_level = params.thread_level || 1;
this.params.title = params.title || false;
this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}};
this.display_show_more = true;
this.thread_list = [];

View File

@ -0,0 +1,122 @@
openerp_mail_followers = function(session, mail) {
var _t = session.web._t,
_lt = session.web._lt;
var mail_followers = session.mail_followers = {};
/**
* ------------------------------------------------------------
* mail_followers Widget
* ------------------------------------------------------------
*
* This widget handles the display of a list of records as a vetical
* list, with an image on the left. The widget itself is a floatting
* right-sided box.
* This widget is mainly used to display the followers of records
* in OpenChatter.
*/
/* Add the widget to registry */
session.web.form.widgets.add('mail_followers', 'openerp.mail_followers.Followers');
mail_followers.Followers = session.web.form.AbstractField.extend({
template: 'mail.followers',
init: function() {
this._super.apply(this, arguments);
this.params = {};
this.params.image = this.node.attrs.image || 'image_small';
this.params.title = this.node.attrs.title || 'Followers';
this.params.display_followers = true;
this.params.display_control = this.node.attrs.display_control || false;
this.params.display_actions = this.node.attrs.display_actions || false;
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
},
start: function() {
var self = this;
// NB: all the widget should be modified to check the actual_mode property on view, not use
// any other method to know if the view is in create mode anymore
this.view.on("change:actual_mode", this, this._check_visibility);
this._check_visibility();
this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); });
if (! this.params.display_control) {
this.$element.find('button.oe_mail_button_followers').hide(); }
this.$element.find('button.oe_mail_button_follow').click(function () { self.do_follow(); })
.mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
.mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
.mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
.mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
this.reinit();
},
_check_visibility: function() {
this.$element.toggle(this.view.get("actual_mode") !== "create");
},
destroy: function () {
this._super.apply(this, arguments);
},
reinit: function() {
this.params.display_followers = true;
this.params.display_control = this.node.attrs.display_control || false;
this.params.display_actions = this.node.attrs.display_actions || false;
this.$element.find('button.oe_mail_button_followers').html('Hide followers')
this.$element.find('button.oe_mail_button_follow').hide();
this.$element.find('button.oe_mail_button_unfollow').hide();
},
set_value: function(value_) {
this.reinit();
if (! this.view.datarecord.id ||
session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
this.$element.find('div.oe_mail_recthread_aside').hide();
return;
}
return this.fetch_subscribers(value_);
},
fetch_subscribers: function (value_) {
return this.ds_follow.call('read', [value_ || this.get_value(), ['name', this.params.image]]).then(this.proxy('display_subscribers'));
},
/**
* Display the followers.
* TODO: replace the is_subscriber check by fields read */
display_subscribers: function (records) {
var self = this;
this.is_subscriber = false;
var user_list = this.$element.find('ul.oe_mail_followers_display').empty();
this.$element.find('div.oe_mail_recthread_followers h4').html(this.params.title + ' (' + records.length + ')');
_(records).each(function (record) {
if (record.id == self.session.uid) { self.is_subscriber = true; }
record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.users', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(user_list);
});
if (this.is_subscriber) {
this.$element.find('button.oe_mail_button_follow').hide();
this.$element.find('button.oe_mail_button_unfollow').show(); }
else {
this.$element.find('button.oe_mail_button_follow').show();
this.$element.find('button.oe_mail_button_unfollow').hide(); }
},
do_follow: function () {
return this.ds_model.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value'));
},
do_unfollow: function () {
return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value'));
},
do_toggle_followers: function () {
this.params.see_subscribers = ! this.params.see_subscribers;
if (this.params.see_subscribers) { this.$element.find('button.oe_mail_button_followers').html('Hide followers'); }
else { this.$element.find('button.oe_mail_button_followers').html('Show followers'); }
this.$element.find('div.oe_mail_recthread_followers').toggle();
},
});
};

View File

@ -61,28 +61,8 @@
<div class="oe_mail_recthread_main">
<!-- contains the document thread -->
</div>
<div class="oe_mail_recthread_aside">
<div class="oe_mail_recthread_actions">
<button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
<button type="button" class="oe_mail_button_followers">Show followers</button>
</div>
<div class="oe_mail_recthread_followers">
<h4>Followers</h4>
<ul class="oe_mail_followers_display"></ul>
</div>
</div>
</div>
<!--
record_thread.subscriber template
Template used to display a subscriber.
-->
<li t-name="mail.record_thread.subscriber">
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.users&amp;id=#{record.id}"><t t-raw="record.name"/></a>
</li>
<!--
mail.compose_message template
This template holds the composition form to write a note or send

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<!--
followers main template
Template used to display the followers and the actions in a record.
-->
<div t-name="mail.followers" class="oe_mail_recthread_aside">
<div class="oe_mail_recthread_actions">
<button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
<button type="button" class="oe_mail_button_followers">Show followers</button>
</div>
<div class="oe_mail_recthread_followers">
<t t-if="widget.params.title">
<h4><t t-raw="widget.params.title"/></h4>
</t>
<ul class="oe_mail_followers_display"></ul>
</div>
</div>
<!--
followers.partner template
Template used to display a partner following the record
-->
<li t-name="mail.followers.partner">
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.users&amp;id=#{record.id}"><t t-raw="record.name"/></a>
</li>
</template>

View File

@ -1046,13 +1046,10 @@ class mrp_production(osv.osv):
# OpenChatter methods and notifications
# ---------------------------------------------------
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add responsible user. """
user_ids = super(mrp_production, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' to the monitored fields """
res = super(mrp_production, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id']
def create_send_note(self, cr, uid, ids, context=None):
self.message_append_note(cr, uid, ids, body=_("Manufacturing order has been <b>created</b>."), context=context)

View File

@ -818,6 +818,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -108,6 +108,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -190,6 +190,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -6,7 +6,7 @@ access_widget_manager,access.portal.widget.manager,model_res_portal_widget,group
access_mail_message,mail.message,mail.model_mail_message,group_portal_member,1,0,1,1
access_mail_message_all,mail.message.all,mail.model_mail_message,group_portal_member,1,0,0,0
access_mail_thread,mail.thread,mail.model_mail_thread,group_portal_member,1,0,0,0
access_mail_subscription,mail.subscription,mail.model_mail_subscription,group_portal_member,1,0,1,1
access_mail_followers,mail.followers,mail.model_mail_followers,group_portal_member,1,0,1,1
access_mail_notification,mail.notification,mail.model_mail_notification,group_portal_member,1,0,1,0
access_mail_group,mail.group,mail.model_mail_group,group_portal_member,1,0,0,0
access_mail_alias,mail.alias,mail.model_mail_alias,group_portal_member,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
6 access_mail_message mail.message mail.model_mail_message group_portal_member 1 0 1 1
7 access_mail_message_all mail.message.all mail.model_mail_message group_portal_member 1 0 0 0
8 access_mail_thread mail.thread mail.model_mail_thread group_portal_member 1 0 0 0
9 access_mail_subscription access_mail_followers mail.subscription mail.followers mail.model_mail_subscription mail.model_mail_followers group_portal_member 1 0 1 1
10 access_mail_notification mail.notification mail.model_mail_notification group_portal_member 1 0 1 0
11 access_mail_group mail.group mail.model_mail_group group_portal_member 1 0 0 0
12 access_mail_alias mail.alias mail.model_mail_alias group_portal_member 1 0 0 0

View File

@ -104,6 +104,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -176,6 +176,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -514,13 +514,10 @@ def Project():
# OpenChatter methods and notifications
# ------------------------------------------------
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add responsible user. """
user_ids = super(project, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' to the monitored fields """
res = super(project, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id']
def create(self, cr, uid, vals, context=None):
if context is None: context = {}
@ -1204,15 +1201,10 @@ class task(base_stage, osv.osv):
result[obj.id].append(obj.user_id.id)
return result
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add responsible user and project manager. """
user_ids = super(task, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
if obj.manager_id and not obj.manager_id.id in user_ids:
user_ids.append(obj.manager_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' and 'manager_id' to the monitored fields """
res = super(task, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id', 'manager_id']
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """

View File

@ -148,6 +148,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>
@ -481,6 +482,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -499,13 +499,10 @@ class project_issue(base_stage, osv.osv):
# OpenChatter methods and notifications
# -------------------------------------------------------
def message_get_subscribers(self, cr, uid, ids, context=None):
""" Override to add responsible user. """
user_ids = super(project_issue, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id and not obj.user_id.id in user_ids:
user_ids.append(obj.user_id.id)
return user_ids
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' to the monitored fields """
res = super(project_issue, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id']
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """

View File

@ -160,6 +160,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -738,12 +738,16 @@ class purchase_order(osv.osv):
result[obj.id].append(obj.validator.id)
return result
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'validator' to the monitored fields """
res = super(purchase_order, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['validator']
def create_send_note(self, cr, uid, ids, context=None):
return self.message_append_note(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_subscribe(cr, uid, [obj.id], [obj.validator.id], context=context)
self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
def shipment_send_note(self, cr, uid, ids, picking_id, context=None):

View File

@ -261,6 +261,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -103,6 +103,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -1031,7 +1031,6 @@ class sale_order(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_subscribe(cr, uid, [obj.id], [obj.user_id.id], context=context)
self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
def confirm_send_note(self, cr, uid, ids, context=None):

View File

@ -351,6 +351,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
</field>

View File

@ -1011,6 +1011,7 @@
<xpath expr="/form/sheet" position="after">
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</xpath>
</data>
@ -1133,6 +1134,7 @@
<xpath expr="/form/sheet" position="after">
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</xpath>
</data>