[IMP] factorisation of objects
bzr revid: fp@openerp.com-20120815170822-udsmc9geqmm6jae8
This commit is contained in:
parent
eb0669850d
commit
af93ab5290
|
@ -21,10 +21,10 @@
|
|||
|
||||
import mail_alias
|
||||
import mail_message
|
||||
import mail_mail
|
||||
import mail_thread
|
||||
import mail_group
|
||||
import mail_subscription
|
||||
import ir_needaction
|
||||
import res_users
|
||||
import res_partner
|
||||
import report
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# 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
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv
|
||||
|
||||
class ir_needaction_mixin(osv.Model):
|
||||
""" Update of ir.needaction_mixin class
|
||||
- override the get_needaction_user_ids method to define the default
|
||||
mail gateway need_action: when the object is unread, the object
|
||||
responsible has an action to perform.
|
||||
"""
|
||||
_name = 'ir.needaction_mixin'
|
||||
_inherit = ['ir.needaction_mixin']
|
||||
|
||||
def get_needaction_user_ids(self, cr, uid, ids, context=None):
|
||||
""" Returns the user_ids that have to perform an action. It the
|
||||
document mail state is unread (False), return object.user_id.id
|
||||
as need_action uid.
|
||||
:return: dict { record_id: [user_ids], }
|
||||
"""
|
||||
result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.message_state == False and obj.user_id:
|
||||
result[obj.id].append(obj.user_id.id)
|
||||
return result
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -34,13 +34,8 @@ class mail_group(osv.osv):
|
|||
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'
|
||||
_name = 'mail.group'
|
||||
_inherit = ['mail.thread']
|
||||
|
@ -51,42 +46,15 @@ class mail_group(osv.osv):
|
|||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
result[obj.id] = tools.image_get_resized_images(obj.image)
|
||||
return result
|
||||
|
||||
|
||||
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):
|
||||
result = {}
|
||||
for id in ids:
|
||||
lower_date = (DT.datetime.now() - DT.timedelta(days=30)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
result[id] = self.message_search(cr, uid, [id], limit=None, domain=[('date', '>=', lower_date)], count=True, context=context)
|
||||
return result
|
||||
|
||||
|
||||
def _get_default_image(self, cr, uid, context=None):
|
||||
image_path = openerp.modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
|
||||
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',
|
||||
|
@ -121,17 +89,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',
|
||||
string='Messages count for last month'),
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade",
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade",
|
||||
help="The email address associated with this group. New emails received will automatically "
|
||||
"create new topics."),
|
||||
}
|
||||
|
@ -150,7 +108,7 @@ class mail_group(osv.osv):
|
|||
'responsible_id': (lambda s, cr, uid, ctx: uid),
|
||||
'image': _get_default_image,
|
||||
'parent_id': _get_menu_parent,
|
||||
'alias_domain': False, # always hide alias during creation
|
||||
'alias_domain': False, # always hide alias during creation
|
||||
}
|
||||
|
||||
def _subscribe_user_with_group_m2m_command(self, cr, uid, ids, group_ids_command, context=None):
|
||||
|
@ -169,7 +127,7 @@ class mail_group(osv.osv):
|
|||
mail_alias = self.pool.get('mail.alias')
|
||||
if not vals.get('alias_id'):
|
||||
vals.pop('alias_name', None) # prevent errors during copy()
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid,
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid,
|
||||
# Using '+' allows using subaddressing for those who don't
|
||||
# have a catchall domain setup.
|
||||
{'alias_name': "group+"+vals['name']},
|
||||
|
@ -194,7 +152,7 @@ class mail_group(osv.osv):
|
|||
self.write(cr, uid, [mail_group_id], {'action': 'ir.actions.client,'+str(newref), 'mail_group_id': mail_group_id}, context=context)
|
||||
|
||||
mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
|
||||
|
||||
|
||||
if vals.get('group_ids'):
|
||||
self._subscribe_user_with_group_m2m_command(cr, uid, [mail_group_id], vals.get('group_ids'), context=context)
|
||||
|
||||
|
|
|
@ -21,18 +21,17 @@
|
|||
</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_subscriber.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>
|
||||
<span style="display: none;"><field name="message_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="last_month_msg_nbr"/> messages last month</li>
|
||||
<li><t t-raw="record.message_summary.raw_value"/></li>
|
||||
<li t-if="! record.message_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.message_is_subscriber.raw_value"><a name="action_group_leave" string="Join" type="object" class="oe_group_leave">Following</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
|
||||
class mail_mail(osv.Model):
|
||||
"""
|
||||
Model holding RFC2822 email messages to send. This model also provides
|
||||
facilities to queue and send new email messages.
|
||||
"""
|
||||
|
||||
_name = 'mail.mail'
|
||||
_description = 'Outgoing Mails'
|
||||
_inherits = {'mail.message': 'message_id'}
|
||||
_columns = {
|
||||
'message_id': fields.many2one('mail.message', 'Message', required=True),
|
||||
# why do we need this on the mail, why not on the company?
|
||||
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
|
||||
'state': fields.selection([
|
||||
('outgoing', 'Outgoing'),
|
||||
('sent', 'Sent'),
|
||||
('received', 'Received'),
|
||||
('exception', 'Delivery Failed'),
|
||||
('cancel', 'Cancelled'),
|
||||
], 'Status', readonly=True),
|
||||
'auto_delete': fields.boolean('Auto Delete',
|
||||
help="Permanently delete this email after sending it, to save space"),
|
||||
|
||||
# FP Note: to be removed, in attachment if needed
|
||||
'original': fields.binary('Original', readonly=1,
|
||||
help="Original version of the message, as it was sent on the network"),
|
||||
# End FP Note
|
||||
|
||||
# FP Note: I propose to remove these fields and put email in attachment as an option
|
||||
'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences.'),
|
||||
'email_to': fields.text('To', help='Message recipients'),
|
||||
'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
|
||||
'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),
|
||||
'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
|
||||
'content_subtype': fields.char('Message content subtype', size=32,
|
||||
oldname="subtype", readonly=1,
|
||||
help="Type of message, usually 'html' or 'plain', used to select "\
|
||||
"plain-text or rich-text contents accordingly"),
|
||||
'body_html': fields.html('Rich-text Contents', help="Rich-text/HTML version of the message"),
|
||||
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': 'received',
|
||||
'content_subtype': 'plain',
|
||||
}
|
||||
|
||||
# FP Note: do we need this method ?
|
||||
# We should just ask to create the message before scheduling it
|
||||
def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, type='email',
|
||||
email_cc=None, email_bcc=None, reply_to=False, partner_ids=None, attachments=None,
|
||||
message_id=False, references=False, res_id=False, content_subtype='plain',
|
||||
headers=None, mail_server_id=False, auto_delete=False, context=None):
|
||||
""" Schedule sending a new email message, to be sent the next time the
|
||||
mail scheduler runs, or the next time :meth:`process_email_queue` is
|
||||
called explicitly.
|
||||
|
||||
:param string email_from: sender email address
|
||||
:param list email_to: list of recipient addresses (to be joined with commas)
|
||||
:param string subject: email subject (no pre-encoding/quoting necessary)
|
||||
:param string body: email body, according to the ``content_subtype``
|
||||
(by default, plaintext). If html content_subtype is used, the
|
||||
message will be automatically converted to plaintext and wrapped
|
||||
in multipart/alternative.
|
||||
:param list email_cc: optional list of string values for CC header
|
||||
(to be joined with commas)
|
||||
:param list email_bcc: optional list of string values for BCC header
|
||||
(to be joined with commas)
|
||||
:param string model: optional model name of the document this mail
|
||||
is related to (this will also be used to generate a tracking id,
|
||||
used to match any response related to the same document)
|
||||
:param int res_id: optional resource identifier this mail is related
|
||||
to (this will also be used to generate a tracking id, used to
|
||||
match any response related to the same document)
|
||||
:param string reply_to: optional value of Reply-To header
|
||||
:param partner_ids: destination partner_ids
|
||||
:param string content_subtype: optional mime content_subtype for
|
||||
the text body (usually 'plain' or 'html'), must match the format
|
||||
of the ``body`` parameter. Default is 'plain', making the content
|
||||
part of the mail "text/plain".
|
||||
:param dict attachments: map of filename to filecontents, where
|
||||
filecontents is a string containing the bytes of the attachment
|
||||
:param dict headers: optional map of headers to set on the outgoing
|
||||
mail (may override the other headers, including Subject,
|
||||
Reply-To, Message-Id, etc.)
|
||||
:param int mail_server_id: optional id of the preferred outgoing
|
||||
mail server for this mail
|
||||
:param bool auto_delete: optional flag to turn on auto-deletion of
|
||||
the message after it has been successfully sent (default to False)
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if attachments is None:
|
||||
attachments = {}
|
||||
if partner_ids is None:
|
||||
partner_ids = []
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
for param in (email_to, email_cc, email_bcc):
|
||||
if param and not isinstance(param, list):
|
||||
param = [param]
|
||||
msg_vals = {
|
||||
'subject': subject,
|
||||
'date': fields.datetime.now(),
|
||||
'user_id': uid,
|
||||
'model': model,
|
||||
'res_id': res_id,
|
||||
'type': type,
|
||||
'body_text': body if content_subtype != 'html' else False,
|
||||
'body_html': body if content_subtype == 'html' else False,
|
||||
'email_from': email_from,
|
||||
'email_to': email_to and ','.join(email_to) or '',
|
||||
'email_cc': email_cc and ','.join(email_cc) or '',
|
||||
'email_bcc': email_bcc and ','.join(email_bcc) or '',
|
||||
'partner_ids': partner_ids,
|
||||
'reply_to': reply_to,
|
||||
'message_id': message_id,
|
||||
'references': references,
|
||||
'content_subtype': content_subtype,
|
||||
'headers': headers, # serialize the dict on the fly
|
||||
'mail_server_id': mail_server_id,
|
||||
'state': 'outgoing',
|
||||
'auto_delete': auto_delete
|
||||
}
|
||||
email_msg_id = self.create(cr, uid, msg_vals, context)
|
||||
attachment_ids = []
|
||||
for fname, fcontent in attachments.iteritems():
|
||||
attachment_data = {
|
||||
'name': fname,
|
||||
'datas_fname': fname,
|
||||
'datas': fcontent and fcontent.encode('base64'),
|
||||
'res_model': self._name,
|
||||
'res_id': email_msg_id,
|
||||
}
|
||||
if context.has_key('default_type'):
|
||||
del context['default_type']
|
||||
attachment_ids.append(attachment_obj.create(cr, uid, attachment_data, context))
|
||||
if attachment_ids:
|
||||
self.write(cr, uid, email_msg_id, { 'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
||||
return email_msg_id
|
||||
|
||||
def mark_outgoing(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
|
||||
|
||||
def process_email_queue(self, cr, uid, ids=None, context=None):
|
||||
"""Send immediately queued messages, committing after each
|
||||
message is sent - this is not transactional and should
|
||||
not be called during another transaction!
|
||||
|
||||
:param list ids: optional list of emails ids to send. If passed
|
||||
no search is performed, and these ids are used
|
||||
instead.
|
||||
:param dict context: if a 'filters' key is present in context,
|
||||
this value will be used as an additional
|
||||
filter to further restrict the outgoing
|
||||
messages to send (by default all 'outgoing'
|
||||
messages are sent).
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not ids:
|
||||
filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
|
||||
if 'filters' in context:
|
||||
filters.extend(context['filters'])
|
||||
ids = self.search(cr, uid, filters, context=context)
|
||||
res = None
|
||||
try:
|
||||
# Force auto-commit - this is meant to be called by
|
||||
# the scheduler, and we can't allow rolling back the status
|
||||
# of previously sent emails!
|
||||
res = self.send(cr, uid, ids, auto_commit=True, context=context)
|
||||
except Exception:
|
||||
_logger.exception("Failed processing mail queue")
|
||||
return res
|
||||
|
||||
def _postprocess_sent_message(self, cr, uid, message, context=None):
|
||||
"""Perform any post-processing necessary after sending ``message``
|
||||
successfully, including deleting it completely along with its
|
||||
attachment if the ``auto_delete`` flag of the message was set.
|
||||
Overridden by subclasses for extra post-processing behaviors.
|
||||
|
||||
:param browse_record message: the message that was just sent
|
||||
:return: True
|
||||
"""
|
||||
if message.auto_delete:
|
||||
self.pool.get('ir.attachment').unlink(cr, uid,
|
||||
[x.id for x in message.attachment_ids
|
||||
if x.res_model == self._name and x.res_id == message.id],
|
||||
context=context)
|
||||
message.unlink()
|
||||
return True
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, context=None):
|
||||
"""Sends the selected emails immediately, ignoring their current
|
||||
state (mails that have already been sent should not be passed
|
||||
unless they should actually be re-sent).
|
||||
Emails successfully delivered are marked as 'sent', and those
|
||||
that fail to be deliver are marked as 'exception', and the
|
||||
corresponding error message is output in the server logs.
|
||||
|
||||
:param bool auto_commit: whether to force a commit of the message
|
||||
status after sending each message (meant
|
||||
only for processing by the scheduler),
|
||||
should never be True during normal
|
||||
transactions (default: False)
|
||||
:return: True
|
||||
"""
|
||||
ir_mail_server = self.pool.get('ir.mail_server')
|
||||
self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
try:
|
||||
attachments = []
|
||||
for attach in message.attachment_ids:
|
||||
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
|
||||
|
||||
body = message.body_html if message.content_subtype == 'html' else message.body_text
|
||||
body_alternative = None
|
||||
content_subtype_alternative = None
|
||||
if message.content_subtype == 'html' and message.body_text:
|
||||
# we have a plain text alternative prepared, pass it to
|
||||
# build_message instead of letting it build one
|
||||
body_alternative = message.body_text
|
||||
content_subtype_alternative = 'plain'
|
||||
|
||||
# handle destination_partners
|
||||
partner_ids_email_to = ''
|
||||
for partner in message.partner_ids:
|
||||
partner_ids_email_to += '%s ' % (partner.email or '')
|
||||
message_email_to = '%s %s' % (partner_ids_email_to, message.email_to or '')
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it
|
||||
# without queuing
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from=message.email_from,
|
||||
email_to=mail_tools_to_email(message_email_to),
|
||||
subject=message.subject,
|
||||
body=body,
|
||||
body_alternative=body_alternative,
|
||||
email_cc=mail_tools_to_email(message.email_cc),
|
||||
email_bcc=mail_tools_to_email(message.email_bcc),
|
||||
reply_to=message.reply_to,
|
||||
attachments=attachments, message_id=message.message_id,
|
||||
references = message.references,
|
||||
object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
|
||||
subtype=message.content_subtype,
|
||||
subtype_alternative=content_subtype_alternative,
|
||||
headers=message.headers and ast.literal_eval(message.headers))
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=message.mail_server_id.id,
|
||||
context=context)
|
||||
if res:
|
||||
message.write({'state':'sent', 'message_id': res, 'email_to': message_email_to})
|
||||
else:
|
||||
message.write({'state':'exception', 'email_to': message_email_to})
|
||||
message.refresh()
|
||||
if message.state == 'sent':
|
||||
self._postprocess_sent_message(cr, uid, message, context=context)
|
||||
except Exception:
|
||||
_logger.exception('failed sending mail.message %s', message.id)
|
||||
message.write({'state':'exception'})
|
||||
|
||||
if auto_commit == True:
|
||||
cr.commit()
|
||||
return True
|
||||
|
|
@ -54,93 +54,15 @@ def mail_tools_to_email(text):
|
|||
if not text: return []
|
||||
return re.findall(r'([^ ,<@]+@[^> ,]+)', text)
|
||||
|
||||
# TODO: remove that after cleaning
|
||||
def to_email(text):
|
||||
return mail_tools_to_email(text)
|
||||
|
||||
# FP Note: I propose to merge mail.message and mail.message.common
|
||||
# there is a conflict for the field parent_id
|
||||
# I don't understand the usage of mail.message.common
|
||||
class mail_message_common(osv.TransientModel):
|
||||
""" Common abstract class for holding the main attributes of a
|
||||
message object. It could be reused as parent model for any
|
||||
database model or wizard screen that needs to hold a kind of
|
||||
message.
|
||||
All internal logic should be in another model while this
|
||||
model holds the basics of a message. For example, a wizard for writing
|
||||
emails should inherit from this class and not from mail.message."""
|
||||
|
||||
def get_record_name(self, cr, uid, ids, name, arg, context=None):
|
||||
result = dict.fromkeys(ids, '')
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
if not message.model or not message.res_id:
|
||||
continue
|
||||
result[message.id] = self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1]
|
||||
return result
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
res = []
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
name = ''
|
||||
if message.subject:
|
||||
name = '%s: ' % (message.subject)
|
||||
if message.body_text:
|
||||
name = name + message.body_text[0:20]
|
||||
res.append((message.id, name))
|
||||
return res
|
||||
|
||||
_name = 'mail.message.common'
|
||||
_rec_name = 'subject'
|
||||
_columns = {
|
||||
'model': fields.char('Related Document Model', size=128, select=1),
|
||||
'res_id': fields.integer('Related Document ID', select=1),
|
||||
'record_name': fields.function(get_record_name, type='string',
|
||||
string='Message Record Name',
|
||||
help="Name get of the related document."),
|
||||
|
||||
'subject': fields.char('Subject', size=128),
|
||||
'date': fields.datetime('Date'),
|
||||
|
||||
# FP Note: I propose to remove these fields and put email in attachment as an option
|
||||
'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences.'),
|
||||
'email_to': fields.text('To', help='Message recipients'),
|
||||
'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
|
||||
'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),
|
||||
'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
|
||||
'headers': fields.text('Message Headers', readonly=1,
|
||||
help="Full message headers, e.g. SMTP session headers (usually available on inbound messages only)"),
|
||||
'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
|
||||
'content_subtype': fields.char('Message content subtype', size=32,
|
||||
oldname="subtype", readonly=1,
|
||||
help="Type of message, usually 'html' or 'plain', used to select "\
|
||||
"plain-text or rich-text contents accordingly"),
|
||||
'body_html': fields.html('Rich-text Contents', help="Rich-text/HTML version of the message"),
|
||||
# END FP Note
|
||||
|
||||
'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
|
||||
'body': fields.text('Text Contents', help="Plain-text version of the message", required=True),
|
||||
|
||||
'parent_id': fields.many2one('mail.message.common', 'Parent Message',
|
||||
select=True, ondelete='set null',
|
||||
help="Parent message, used for displaying as threads with hierarchy"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'content_subtype': 'plain',
|
||||
'date': (lambda *a: fields.datetime.now()),
|
||||
}
|
||||
|
||||
class mail_message(osv.Model):
|
||||
"""Model holding messages: system notification (replacing res.log
|
||||
notifications), comments (for OpenChatter feature) and
|
||||
RFC2822 email messages. This model also provides facilities to
|
||||
parse, queue and send new email messages. Type of messages
|
||||
are differentiated using the 'type' column. """
|
||||
notifications), comments (for OpenChatter feature). This model also
|
||||
provides facilities to parse new email messages. Type of messages are
|
||||
differentiated using the 'type' column. """
|
||||
|
||||
_name = 'mail.message'
|
||||
_inherit = 'mail.message.common'
|
||||
_description = 'Mail Message (email, comment, notification)'
|
||||
_order = 'date desc'
|
||||
_description = 'Message'
|
||||
_order = 'id desc'
|
||||
|
||||
# FP Note: can we remove these two methods ?
|
||||
def open_document(self, cr, uid, ids, context=None):
|
||||
|
@ -183,7 +105,28 @@ class mail_message(osv.Model):
|
|||
return action_data
|
||||
# END FP Note
|
||||
|
||||
def get_record_name(self, cr, uid, ids, name, arg, context=None):
|
||||
result = dict.fromkeys(ids, '')
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
if not message.model or not message.res_id:
|
||||
continue
|
||||
result[message.id] = self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1]
|
||||
return result
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
res = []
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
name = ''
|
||||
if message.subject:
|
||||
name = '%s: ' % (message.subject)
|
||||
if message.body_text:
|
||||
name = name + message.body_text[0:20]
|
||||
res.append((message.id, name))
|
||||
return res
|
||||
|
||||
|
||||
_columns = {
|
||||
# should we keep a distinction between email and comment ?
|
||||
'type': fields.selection([
|
||||
('email', 'email'),
|
||||
('comment', 'Comment'),
|
||||
|
@ -191,49 +134,55 @@ class mail_message(osv.Model):
|
|||
], 'Type',
|
||||
help="Message type: email for email message, notification for system "\
|
||||
"message, comment for other messages such as user replies"),
|
||||
# FP Note: ro be removed
|
||||
'partner_id': fields.many2one('res.partner', 'Related partner',
|
||||
help="Deprecated field. Use partner_ids instead."),
|
||||
# END FP Note
|
||||
|
||||
# partner_id should be renamed into author_id
|
||||
'author_id': fields.many2one('res.partner', 'Author', required=True),
|
||||
|
||||
# this is redundant with notifications ?
|
||||
'partner_ids': fields.many2many('res.partner',
|
||||
'mail_message_destination_partner_rel',
|
||||
'message_id', 'partner_id', 'Destination partners',
|
||||
help="When sending emails through the social network composition wizard"\
|
||||
"you may choose to send a copy of the mail to partners."),
|
||||
'user_id': fields.many2one('res.users', 'Author', readonly=1),
|
||||
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'),
|
||||
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
|
||||
'state': fields.selection([
|
||||
('outgoing', 'Outgoing'),
|
||||
('sent', 'Sent'),
|
||||
('received', 'Received'),
|
||||
('exception', 'Delivery Failed'),
|
||||
('cancel', 'Cancelled'),
|
||||
], 'Status', readonly=True),
|
||||
'auto_delete': fields.boolean('Auto Delete',
|
||||
help="Permanently delete this email after sending it, to save space"),
|
||||
|
||||
# FP Note: to be removed, in attachment if needed
|
||||
'original': fields.binary('Original', readonly=1,
|
||||
help="Original version of the message, as it was sent on the network"),
|
||||
# End FP Note
|
||||
# 'user_id': fields.many2one('res.users', 'Author', readonly=1),
|
||||
|
||||
# why don't we attach the file to the message using res_id, res_model of the attachment ?
|
||||
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'),
|
||||
|
||||
'parent_id': fields.many2one('mail.message', 'Parent Message',
|
||||
select=True, ondelete='set null',
|
||||
help="Parent message, used for displaying as threads with hierarchy"),
|
||||
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
|
||||
|
||||
|
||||
'model': fields.char('Related Document Model', size=128, select=1),
|
||||
'res_id': fields.integer('Related Document ID', select=1),
|
||||
'record_name': fields.function(get_record_name, type='string',
|
||||
string='Message Record Name',
|
||||
help="Name get of the related document."),
|
||||
|
||||
'subject': fields.char('Subject', size=128),
|
||||
'date': fields.datetime('Date'),
|
||||
|
||||
# FP Note: do we need this ?
|
||||
'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
|
||||
|
||||
# END FP Note
|
||||
|
||||
'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
|
||||
'body': fields.text('Content', help="Content of Message", required=True),
|
||||
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'type': 'email',
|
||||
'state': 'received',
|
||||
'date': (lambda *a: fields.datetime.now()),
|
||||
}
|
||||
|
||||
|
||||
#------------------------------------------------------
|
||||
# Email api
|
||||
#------------------------------------------------------
|
||||
|
||||
|
||||
def init(self, cr):
|
||||
cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'mail_message_model_res_id_idx'""")
|
||||
if not cr.fetchone():
|
||||
|
@ -260,7 +209,7 @@ class mail_message(osv.Model):
|
|||
mids = self.pool.get(model).exists(cr, uid, mids)
|
||||
ima_obj.check(cr, uid, model, mode)
|
||||
self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context)
|
||||
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
newid = super(mail_message, self).create(cr, uid, values, context)
|
||||
self.check(cr, uid, [newid], mode='create', context=context)
|
||||
|
@ -273,9 +222,7 @@ class mail_message(osv.Model):
|
|||
for follower in modobj.follower_ids:
|
||||
if follower.id <> uid:
|
||||
follower_notify.append(follower.id)
|
||||
|
||||
self.pool.get('mail.notification').notify(cr, uid, follower_notify, newid, context=context)
|
||||
|
||||
return newid
|
||||
|
||||
def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
|
||||
|
@ -289,7 +236,7 @@ class mail_message(osv.Model):
|
|||
self.check(cr, uid, [id], 'read', context=context)
|
||||
default.update(message_id=False, original=False, headers=False)
|
||||
return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
result = super(mail_message, self).write(cr, uid, ids, vals, context)
|
||||
self.check(cr, uid, ids, 'write', context=context)
|
||||
|
@ -299,136 +246,7 @@ class mail_message(osv.Model):
|
|||
self.check(cr, uid, ids, 'unlink', context=context)
|
||||
return super(mail_message, self).unlink(cr, uid, ids, context)
|
||||
|
||||
# FP Note: do we need this method ?
|
||||
# We should just ask to create the message before scheduling it
|
||||
def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, type='email',
|
||||
email_cc=None, email_bcc=None, reply_to=False, partner_ids=None, attachments=None,
|
||||
message_id=False, references=False, res_id=False, content_subtype='plain',
|
||||
headers=None, mail_server_id=False, auto_delete=False, context=None):
|
||||
""" Schedule sending a new email message, to be sent the next time the
|
||||
mail scheduler runs, or the next time :meth:`process_email_queue` is
|
||||
called explicitly.
|
||||
|
||||
:param string email_from: sender email address
|
||||
:param list email_to: list of recipient addresses (to be joined with commas)
|
||||
:param string subject: email subject (no pre-encoding/quoting necessary)
|
||||
:param string body: email body, according to the ``content_subtype``
|
||||
(by default, plaintext). If html content_subtype is used, the
|
||||
message will be automatically converted to plaintext and wrapped
|
||||
in multipart/alternative.
|
||||
:param list email_cc: optional list of string values for CC header
|
||||
(to be joined with commas)
|
||||
:param list email_bcc: optional list of string values for BCC header
|
||||
(to be joined with commas)
|
||||
:param string model: optional model name of the document this mail
|
||||
is related to (this will also be used to generate a tracking id,
|
||||
used to match any response related to the same document)
|
||||
:param int res_id: optional resource identifier this mail is related
|
||||
to (this will also be used to generate a tracking id, used to
|
||||
match any response related to the same document)
|
||||
:param string reply_to: optional value of Reply-To header
|
||||
:param partner_ids: destination partner_ids
|
||||
:param string content_subtype: optional mime content_subtype for
|
||||
the text body (usually 'plain' or 'html'), must match the format
|
||||
of the ``body`` parameter. Default is 'plain', making the content
|
||||
part of the mail "text/plain".
|
||||
:param dict attachments: map of filename to filecontents, where
|
||||
filecontents is a string containing the bytes of the attachment
|
||||
:param dict headers: optional map of headers to set on the outgoing
|
||||
mail (may override the other headers, including Subject,
|
||||
Reply-To, Message-Id, etc.)
|
||||
:param int mail_server_id: optional id of the preferred outgoing
|
||||
mail server for this mail
|
||||
:param bool auto_delete: optional flag to turn on auto-deletion of
|
||||
the message after it has been successfully sent (default to False)
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if attachments is None:
|
||||
attachments = {}
|
||||
if partner_ids is None:
|
||||
partner_ids = []
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
for param in (email_to, email_cc, email_bcc):
|
||||
if param and not isinstance(param, list):
|
||||
param = [param]
|
||||
msg_vals = {
|
||||
'subject': subject,
|
||||
'date': fields.datetime.now(),
|
||||
'user_id': uid,
|
||||
'model': model,
|
||||
'res_id': res_id,
|
||||
'type': type,
|
||||
'body_text': body if content_subtype != 'html' else False,
|
||||
'body_html': body if content_subtype == 'html' else False,
|
||||
'email_from': email_from,
|
||||
'email_to': email_to and ','.join(email_to) or '',
|
||||
'email_cc': email_cc and ','.join(email_cc) or '',
|
||||
'email_bcc': email_bcc and ','.join(email_bcc) or '',
|
||||
'partner_ids': partner_ids,
|
||||
'reply_to': reply_to,
|
||||
'message_id': message_id,
|
||||
'references': references,
|
||||
'content_subtype': content_subtype,
|
||||
'headers': headers, # serialize the dict on the fly
|
||||
'mail_server_id': mail_server_id,
|
||||
'state': 'outgoing',
|
||||
'auto_delete': auto_delete
|
||||
}
|
||||
email_msg_id = self.create(cr, uid, msg_vals, context)
|
||||
attachment_ids = []
|
||||
for fname, fcontent in attachments.iteritems():
|
||||
attachment_data = {
|
||||
'name': fname,
|
||||
'datas_fname': fname,
|
||||
'datas': fcontent and fcontent.encode('base64'),
|
||||
'res_model': self._name,
|
||||
'res_id': email_msg_id,
|
||||
}
|
||||
if context.has_key('default_type'):
|
||||
del context['default_type']
|
||||
attachment_ids.append(attachment_obj.create(cr, uid, attachment_data, context))
|
||||
if attachment_ids:
|
||||
self.write(cr, uid, email_msg_id, { 'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
||||
return email_msg_id
|
||||
|
||||
def mark_outgoing(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
|
||||
|
||||
def process_email_queue(self, cr, uid, ids=None, context=None):
|
||||
"""Send immediately queued messages, committing after each
|
||||
message is sent - this is not transactional and should
|
||||
not be called during another transaction!
|
||||
|
||||
:param list ids: optional list of emails ids to send. If passed
|
||||
no search is performed, and these ids are used
|
||||
instead.
|
||||
:param dict context: if a 'filters' key is present in context,
|
||||
this value will be used as an additional
|
||||
filter to further restrict the outgoing
|
||||
messages to send (by default all 'outgoing'
|
||||
messages are sent).
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not ids:
|
||||
filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
|
||||
if 'filters' in context:
|
||||
filters.extend(context['filters'])
|
||||
ids = self.search(cr, uid, filters, context=context)
|
||||
res = None
|
||||
try:
|
||||
# Force auto-commit - this is meant to be called by
|
||||
# the scheduler, and we can't allow rolling back the status
|
||||
# of previously sent emails!
|
||||
res = self.send(cr, uid, ids, auto_commit=True, context=context)
|
||||
except Exception:
|
||||
_logger.exception("Failed processing mail queue")
|
||||
return res
|
||||
|
||||
# FP Note: to be simplified, mail.message fields only, not mail.mail
|
||||
def parse_message(self, message, save_original=False, context=None):
|
||||
"""Parses a string or email.message.Message representing an
|
||||
RFC-2822 email, and returns a generic dict holding the
|
||||
|
@ -585,96 +403,4 @@ class mail_message(osv.Model):
|
|||
msg['sub_type'] = msg['content_subtype'] or 'plain'
|
||||
return msg
|
||||
|
||||
def _postprocess_sent_message(self, cr, uid, message, context=None):
|
||||
"""Perform any post-processing necessary after sending ``message``
|
||||
successfully, including deleting it completely along with its
|
||||
attachment if the ``auto_delete`` flag of the message was set.
|
||||
Overridden by subclasses for extra post-processing behaviors.
|
||||
|
||||
:param browse_record message: the message that was just sent
|
||||
:return: True
|
||||
"""
|
||||
if message.auto_delete:
|
||||
self.pool.get('ir.attachment').unlink(cr, uid,
|
||||
[x.id for x in message.attachment_ids
|
||||
if x.res_model == self._name and x.res_id == message.id],
|
||||
context=context)
|
||||
message.unlink()
|
||||
return True
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, context=None):
|
||||
"""Sends the selected emails immediately, ignoring their current
|
||||
state (mails that have already been sent should not be passed
|
||||
unless they should actually be re-sent).
|
||||
Emails successfully delivered are marked as 'sent', and those
|
||||
that fail to be deliver are marked as 'exception', and the
|
||||
corresponding error message is output in the server logs.
|
||||
|
||||
:param bool auto_commit: whether to force a commit of the message
|
||||
status after sending each message (meant
|
||||
only for processing by the scheduler),
|
||||
should never be True during normal
|
||||
transactions (default: False)
|
||||
:return: True
|
||||
"""
|
||||
ir_mail_server = self.pool.get('ir.mail_server')
|
||||
self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
try:
|
||||
attachments = []
|
||||
for attach in message.attachment_ids:
|
||||
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
|
||||
|
||||
body = message.body_html if message.content_subtype == 'html' else message.body_text
|
||||
body_alternative = None
|
||||
content_subtype_alternative = None
|
||||
if message.content_subtype == 'html' and message.body_text:
|
||||
# we have a plain text alternative prepared, pass it to
|
||||
# build_message instead of letting it build one
|
||||
body_alternative = message.body_text
|
||||
content_subtype_alternative = 'plain'
|
||||
|
||||
# handle destination_partners
|
||||
partner_ids_email_to = ''
|
||||
for partner in message.partner_ids:
|
||||
partner_ids_email_to += '%s ' % (partner.email or '')
|
||||
message_email_to = '%s %s' % (partner_ids_email_to, message.email_to or '')
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it
|
||||
# without queuing
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from=message.email_from,
|
||||
email_to=mail_tools_to_email(message_email_to),
|
||||
subject=message.subject,
|
||||
body=body,
|
||||
body_alternative=body_alternative,
|
||||
email_cc=mail_tools_to_email(message.email_cc),
|
||||
email_bcc=mail_tools_to_email(message.email_bcc),
|
||||
reply_to=message.reply_to,
|
||||
attachments=attachments, message_id=message.message_id,
|
||||
references = message.references,
|
||||
object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
|
||||
subtype=message.content_subtype,
|
||||
subtype_alternative=content_subtype_alternative,
|
||||
headers=message.headers and ast.literal_eval(message.headers))
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=message.mail_server_id.id,
|
||||
context=context)
|
||||
if res:
|
||||
message.write({'state':'sent', 'message_id': res, 'email_to': message_email_to})
|
||||
else:
|
||||
message.write({'state':'exception', 'email_to': message_email_to})
|
||||
message.refresh()
|
||||
if message.state == 'sent':
|
||||
self._postprocess_sent_message(cr, uid, message, context=context)
|
||||
except Exception:
|
||||
_logger.exception('failed sending mail.message %s', message.id)
|
||||
message.write({'state':'exception'})
|
||||
|
||||
if auto_commit == True:
|
||||
cr.commit()
|
||||
return True
|
||||
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -70,6 +70,19 @@ class mail_thread(osv.Model):
|
|||
_description = 'Email Thread'
|
||||
# TODO: may be we should make it _inherit ir.needaction
|
||||
|
||||
def _get_is_subscriber(self, cr, uid, ids, name, args, context=None):
|
||||
subobj = self.pool.get('mail.subscription')
|
||||
subids = subobj.search(cr, uid, [
|
||||
('res_model','=',self._name),
|
||||
('res_id', 'in', ids),
|
||||
('user_id','=',uid)], context=context)
|
||||
result = dict.fromkeys(ids, False)
|
||||
for sub in subobj.browse(cr, uid, subids, context=context):
|
||||
result[res_id] = True
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def _get_message_data(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
for id in ids:
|
||||
|
@ -98,6 +111,8 @@ class mail_thread(osv.Model):
|
|||
return [('id','in',[])]
|
||||
|
||||
_columns = {
|
||||
'message_is_subscriber': fields.function(_get_is_subscriber,
|
||||
type='boolean', string='Is a Follower'),
|
||||
'message_follower_ids': fields.one2many('mail.subscription', 'res_id',
|
||||
domain=lambda self: [('res_model','=',self._name)],
|
||||
string='Followers')
|
||||
|
|
Loading…
Reference in New Issue