diff --git a/addons/account_followup/__openerp__.py b/addons/account_followup/__openerp__.py index ae6fb8e4fce..c0c78ebae4f 100644 --- a/addons/account_followup/__openerp__.py +++ b/addons/account_followup/__openerp__.py @@ -43,7 +43,7 @@ """, 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', - 'depends': ['account'], + 'depends': ['account', 'email'], 'init_xml': [], 'update_xml': [ 'security/ir.model.access.csv', diff --git a/addons/account_followup/wizard/account_followup_print.py b/addons/account_followup/wizard/account_followup_print.py index 47c9e1a59cb..02f463a78eb 100644 --- a/addons/account_followup/wizard/account_followup_print.py +++ b/addons/account_followup/wizard/account_followup_print.py @@ -205,6 +205,7 @@ class account_followup_print_all(osv.osv_memory): move_obj = self.pool.get('account.move.line') user_obj = self.pool.get('res.users') line_obj = self.pool.get('account_followup.stat') + email_message_obj = self.pool.get('email.message') if context is None: context = {} @@ -277,7 +278,7 @@ class account_followup_print_all(osv.osv_memory): msg = '' if dest: try: - tools.email_send(src, dest, sub, body) + email_message_obj.email_send(cr, uid, src, dest, sub, body, model='account.followup.print.all') msg_sent += partner.name + '\n' except Exception, e: raise osv.except_osv('Error !', e ) diff --git a/addons/base_action_rule/__openerp__.py b/addons/base_action_rule/__openerp__.py index 7717890afba..2611bcee042 100644 --- a/addons/base_action_rule/__openerp__.py +++ b/addons/base_action_rule/__openerp__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). # @@ -15,7 +15,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -26,12 +26,12 @@ 'description': "This module allows to implement action rules for any object.", 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', - 'depends': ['base'], + 'depends': ['base', 'email'], 'init_xml': [ 'base_action_rule_data.xml' ], 'update_xml': [ - 'base_action_rule_view.xml', + 'base_action_rule_view.xml', 'security/ir.model.access.csv', ], 'demo_xml': [], diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 2a4e2c0e4e1..a2432050f1b 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -24,7 +24,7 @@ from tools.translate import _ from datetime import datetime from datetime import timedelta from tools.safe_eval import safe_eval -import pooler +import pooler import re import time import tools @@ -39,7 +39,7 @@ class base_action_rule(osv.osv): _name = 'base.action.rule' _description = 'Action Rules' - + def _state_get(self, cr, uid, context=None): """ Get State @param self: The object pointer @@ -55,7 +55,7 @@ class base_action_rule(osv.osv): @param uid: the current user’s ID for security checks, @param context: A standard dictionary for contextual values """ return [('', '')] - + def priority_get(self, cr, uid, context=None): """ Get Priority @param self: The object pointer @@ -65,57 +65,57 @@ class base_action_rule(osv.osv): return [('', '')] _columns = { - 'name': fields.char('Rule Name', size=64, required=True), - 'model_id': fields.many2one('ir.model', 'Object', required=True), - 'create_date': fields.datetime('Create Date', readonly=1), + 'name': fields.char('Rule Name', size=64, required=True), + 'model_id': fields.many2one('ir.model', 'Object', required=True), + 'create_date': fields.datetime('Create Date', readonly=1), 'active': fields.boolean('Active', help="If the active field is set to False,\ - it will allow you to hide the rule without removing it."), + it will allow you to hide the rule without removing it."), 'sequence': fields.integer('Sequence', help="Gives the sequence order \ -when displaying a list of rules."), +when displaying a list of rules."), 'trg_date_type': fields.selection([ - ('none', 'None'), - ('create', 'Creation Date'), - ('action_last', 'Last Action Date'), - ('date', 'Date'), - ('deadline', 'Deadline'), - ], 'Trigger Date', size=16), + ('none', 'None'), + ('create', 'Creation Date'), + ('action_last', 'Last Action Date'), + ('date', 'Date'), + ('deadline', 'Deadline'), + ], 'Trigger Date', size=16), 'trg_date_range': fields.integer('Delay after trigger date', \ help="Delay After Trigger Date,\ specifies you can put a negative number. If you need a delay before the \ -trigger date, like sending a reminder 15 minutes before a meeting."), +trigger date, like sending a reminder 15 minutes before a meeting."), 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \ - ('day', 'Days'), ('month', 'Months')], 'Delay type'), - 'trg_user_id': fields.many2one('res.users', 'Responsible'), - 'trg_partner_id': fields.many2one('res.partner', 'Partner'), - 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'), - 'trg_state_from': fields.selection(_state_get, 'State', size=16), - 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16), + ('day', 'Days'), ('month', 'Months')], 'Delay type'), + 'trg_user_id': fields.many2one('res.users', 'Responsible'), + 'trg_partner_id': fields.many2one('res.partner', 'Partner'), + 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'), + 'trg_state_from': fields.selection(_state_get, 'State', size=16), + 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16), - 'act_method': fields.char('Call Object Method', size=64), - 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), - 'act_state': fields.selection(_state_get, 'Set State to', size=16), + 'act_method': fields.char('Call Object Method', size=64), + 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), + 'act_state': fields.selection(_state_get, 'Set State to', size=16), 'act_email_cc': fields.char('Add Watchers (Cc)', size=250, help="\ These people will receive a copy of the future communication between partner \ -and users by email"), +and users by email"), 'act_remind_partner': fields.boolean('Remind Partner', help="Check \ -this if you want the rule to send a reminder by email to the partner."), +this if you want the rule to send a reminder by email to the partner."), 'act_remind_user': fields.boolean('Remind Responsible', help="Check \ -this if you want the rule to send a reminder by email to the user."), - 'act_reply_to': fields.char('Reply-To', size=64), - 'act_remind_attach': fields.boolean('Remind with Attachment', help="Check this if you want that all documents attached to the object be attached to the reminder email sent."), +this if you want the rule to send a reminder by email to the user."), + 'act_reply_to': fields.char('Reply-To', size=64), + 'act_remind_attach': fields.boolean('Remind with Attachment', help="Check this if you want that all documents attached to the object be attached to the reminder email sent."), 'act_mail_to_user': fields.boolean('Mail to Responsible', help="Check\ - this if you want the rule to send an email to the responsible person."), - 'act_mail_to_watchers': fields.boolean('Mail to Watchers (CC)', + this if you want the rule to send an email to the responsible person."), + 'act_mail_to_watchers': fields.boolean('Mail to Watchers (CC)', help="Check this if you want \ -the rule to mark CC(mail to any other person defined in actions)."), +the rule to mark CC(mail to any other person defined in actions)."), 'act_mail_to_email': fields.char('Mail to these Emails', size=128, \ - help="Email-id of the persons whom mail is to be sent"), - 'act_mail_body': fields.text('Mail body', help="Content of mail"), + help="Email-id of the persons whom mail is to be sent"), + 'act_mail_body': fields.text('Mail body', help="Content of mail"), 'regex_name': fields.char('Regex on Resource Name', size=128, help="Regular expression for matching name of the resource\ \ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\ -\nNote: This is case sensitive search."), - 'server_action_id': fields.many2one('ir.actions.server', 'Server Action', help="Describes the action name.\neg:on which object which action to be taken on basis of which condition"), - 'filter_id':fields.many2one('ir.filters', 'Filter', required=False), +\nNote: This is case sensitive search."), + 'server_action_id': fields.many2one('ir.actions.server', 'Server Action', help="Describes the action name.\neg:on which object which action to be taken on basis of which condition"), + 'filter_id':fields.many2one('ir.filters', 'Filter', required=False), 'act_email_from' : fields.char('Email From', size=64, required=False, help="Use a python expression to specify the right field on which one than we will use for the 'From' field of the header"), 'act_email_to' : fields.char('Email To', size=64, required=False, @@ -124,17 +124,17 @@ the rule to mark CC(mail to any other person defined in actions)."), } _defaults = { - 'active': lambda *a: True, - 'trg_date_type': lambda *a: 'none', - 'trg_date_range_type': lambda *a: 'day', - 'act_mail_to_user': lambda *a: 0, - 'act_remind_partner': lambda *a: 0, - 'act_remind_user': lambda *a: 0, - 'act_mail_to_watchers': lambda *a: 0, + 'active': lambda *a: True, + 'trg_date_type': lambda *a: 'none', + 'trg_date_range_type': lambda *a: 'day', + 'act_mail_to_user': lambda *a: 0, + 'act_remind_partner': lambda *a: 0, + 'act_remind_user': lambda *a: 0, + 'act_mail_to_watchers': lambda *a: 0, } - + _order = 'sequence' - + def onchange_model_id(self, cr, uid, ids, name): #This is not a good solution as it will affect the domain only on onchange res = {'domain':{'filter_id':[]}} @@ -174,7 +174,7 @@ the rule to mark CC(mail to any other person defined in actions)."), self.pre_action(cr, uid, [new_id], model, context=context) return new_id return make_call_old - + def _write(self, old_write, model, context=None): if context is None: context = {} @@ -202,9 +202,9 @@ the rule to mark CC(mail to any other person defined in actions)."), return True def create(self, cr, uid, vals, context=None): res_id = super(base_action_rule, self).create(cr, uid, vals, context=context) - self._register_hook(cr, uid, [res_id], context=context) + self._register_hook(cr, uid, [res_id], context=context) return res_id - + def write(self, cr, uid, ids, vals, context=None): res = super(base_action_rule, self).write(cr, uid, ids, vals, context=context) self._register_hook(cr, uid, ids, context=context) @@ -272,18 +272,18 @@ the rule to mark CC(mail to any other person defined in actions)."), @param self: The object pointer """ data = { - 'object_id': obj.id, - 'object_subject': hasattr(obj, 'name') and obj.name or False, - 'object_date': hasattr(obj, 'date') and obj.date or False, - 'object_description': hasattr(obj, 'description') and obj.description or False, - 'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/', + 'object_id': obj.id, + 'object_subject': hasattr(obj, 'name') and obj.name or False, + 'object_date': hasattr(obj, 'date') and obj.date or False, + 'object_description': hasattr(obj, 'description') and obj.description or False, + 'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/', 'object_user_email': hasattr(obj, 'user_id') and (obj.user_id and \ - obj.user_id.address_id and obj.user_id.address_id.email) or '/', + obj.user_id.address_id and obj.user_id.address_id.email) or '/', 'object_user_phone': hasattr(obj, 'user_id') and (obj.user_id and\ - obj.user_id.address_id and obj.user_id.address_id.phone) or '/', - 'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/', + obj.user_id.address_id and obj.user_id.address_id.phone) or '/', + 'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/', 'partner_email': hasattr(obj, 'partner_address_id') and (obj.partner_address_id and\ - obj.partner_address_id.email) or '/', + obj.partner_address_id.email) or '/', } return self.format_body(body % data) @@ -302,6 +302,7 @@ the rule to mark CC(mail to any other person defined in actions)."), if context is None: context = {} + email_message_obj = self.pool.get('email.message') body = self.format_mail(obj, body) if not emailfrom: if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.address_id and\ @@ -312,9 +313,9 @@ the rule to mark CC(mail to any other person defined in actions)."), emailfrom = tools.ustr(emailfrom) reply_to = emailfrom if not emailfrom: - raise osv.except_osv(_('Error!'), + raise osv.except_osv(_('Error!'), _("No E-Mail ID Found for your Company address!")) - return tools.email_send(emailfrom, emails, name, body, reply_to=reply_to, openobject_id=str(obj.id)) + return email_message_obj.email_send(cr, uid, emailfrom, emails, name, body, model='base.action.rule', reply_to=reply_to, openobject_id=str(obj.id)) def do_check(self, cr, uid, action, obj, context=None): @@ -325,7 +326,7 @@ the rule to mark CC(mail to any other person defined in actions)."), @param context: A standard dictionary for contextual values """ if context is None: context = {} - ok = True + ok = True if action.filter_id: if action.model_id.model == action.filter_id.model_id: context.update(eval(action.filter_id.context)) @@ -480,15 +481,15 @@ the rule to mark CC(mail to any other person defined in actions)."), return True _constraints = [ - (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']), + (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']), ] base_action_rule() class ir_cron(osv.osv): - _inherit = 'ir.cron' - + _inherit = 'ir.cron' + def _poolJobs(self, db_name, check=False): try: db = pooler.get_db(db_name) diff --git a/addons/base_calendar/__openerp__.py b/addons/base_calendar/__openerp__.py index 2d94e61beb8..7b53b0d7a36 100644 --- a/addons/base_calendar/__openerp__.py +++ b/addons/base_calendar/__openerp__.py @@ -20,9 +20,9 @@ ############################################################################## { - "name" : "Basic Calendar Functionality", - "version" : "1.0", - "depends" : ["base"], + "name" : "Basic Calendar Functionality", + "version" : "1.0", + "depends" : ["base", "email"], 'description': """Full featured calendar system that supports: - Calendar of events - Alerts (create requests) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index 9693a0d5c0f..7cfed6711b4 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -411,7 +411,7 @@ property or property parameter."), cal = vobject.iCalendar() event = cal.add('vevent') if not event_obj.date_deadline or not event_obj.date: - raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!")) + raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!")) event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S')) event.add('dtstart').value = ics_datetime(event_obj.date) event.add('dtend').value = ics_datetime(event_obj.date_deadline) @@ -456,7 +456,7 @@ property or property parameter."), trigger.value = delta # Compute other details valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP' - + for attendee in event_obj.attendee_ids: attendee_add = event.add('attendee') attendee_add.params['CUTYPE'] = [str(attendee.cutype)] @@ -480,6 +480,7 @@ property or property parameter."), context = {} company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name + email_message_obj = self.pool.get('email.message') for att in self.browse(cr, uid, ids, context=context): sign = att.sent_by_uid and att.sent_by_uid.signature or '' sign = '
'.join(sign and sign.split('\n') or []) @@ -506,11 +507,12 @@ property or property parameter."), body = html_invitation % body_vals if mail_to and email_from: attach = self.get_ics_file(cr, uid, res_obj, context=context) - tools.email_send( + email_message_obj.email_send(cr, uid, email_from, mail_to, sub, body, + model='calendar.attendee', attach=attach and [('invitation.ics', attach)] or None, subtype='html', reply_to=email_from @@ -674,7 +676,7 @@ true, it will allow you to hide the event alarm information without removing it. new_res_alarm = alarm_ids[0] cr.execute('UPDATE %s ' % model_obj._table + \ ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \ - ' WHERE id=%s', + ' WHERE id=%s', (cal_alarm.id, new_res_alarm, data.id)) self.do_alarm_unlink(cr, uid, [data.id], model) @@ -809,6 +811,7 @@ class calendar_alarm(osv.osv): return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!! if context is None: context = {} + email_message_obj = self.pool.get('email.message') current_datetime = datetime.now() request_obj = self.pool.get('res.request') alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context) @@ -890,11 +893,12 @@ From: for att in alarm.attendee_ids: mail_to.append(att.user_id.address_id.email) if mail_to: - tools.email_send( + email_message_obj.email_send(cr, uid, tools.config.get('email_from', False), mail_to, sub, - body + body, + model='calendar.alarm' ) if next_trigger_date: update_vals.update({'trigger_date': next_trigger_date}) @@ -966,7 +970,7 @@ class calendar_event(osv.osv): value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S") elif end_date and duration and not allday: # we have both, keep them synchronized: - # set duration based on end_date (arbitrary decision: this avoid + # set duration based on end_date (arbitrary decision: this avoid # getting dates like 06:31:48 instead of 06:32:00) end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") diff = end - start @@ -1130,7 +1134,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'), 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \ ('weekly', 'Weekly'), ('monthly', 'Monthly'), \ - ('yearly', 'Yearly'),], + ('yearly', 'Yearly'),], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"), 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]}, @@ -1148,7 +1152,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ ('weekly', 'Weeks'), ('monthly', 'Months'), ('yearly', 'Years'), ], 'Frequency'), - + 'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'), 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"), 'count': fields.integer('Repeat', help="Repeat x times"), @@ -1159,7 +1163,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ 'fr': fields.boolean('Fri'), 'sa': fields.boolean('Sat'), 'su': fields.boolean('Sun'), - 'select1': fields.selection([('date', 'Date of month'), + 'select1': fields.selection([('date', 'Date of month'), ('day', 'Day of month')], 'Option'), 'day': fields.integer('Date of month'), 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \ @@ -1176,8 +1180,8 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}), 'active': fields.boolean('Active', help="If the active field is set to \ true, it will allow you to hide the event alarm information without removing it."), - 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"), - 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."), + 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"), + 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."), } def default_organizer(self, cr, uid, context=None): user_pool = self.pool.get('res.users') @@ -1204,12 +1208,12 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None): if not context: context = {} - + value = {} if edit_all and rrule_type: for id in ids: base_calendar_id2real_id(id) - return value + return value def modify_all(self, cr, uid, event_ids, defaults, context=None, *args): """ @@ -1342,7 +1346,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ freq=datas.get('rrule_type') if freq == 'none': return '' - + interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or '' if freq == 'weekly': @@ -1358,7 +1362,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ elif datas.get('select1')=='date': monthstring = ';BYMONTHDAY=' + str(datas.get('day')) - + if datas.get('end_date'): datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z' enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\ @@ -1403,7 +1407,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit) return res - + def get_edit_all(self, cr, uid, id, vals=None): """ @@ -1414,10 +1418,10 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ if(vals and 'edit_all' in vals): #we jsut check edit_all return vals['edit_all'] else: #it's a recurrent event and edit_all is already check - return meeting['recurrency'] and meeting['edit_all'] + return meeting['recurrency'] and meeting['edit_all'] + - def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True): """ @@ -1440,12 +1444,12 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ res = False for event_id in select: real_event_id = base_calendar_id2real_id(event_id) - + if(self.get_edit_all(cr, uid, event_id, vals=vals)): event_id = real_event_id - - + + if len(str(event_id).split('-')) > 1: data = self.read(cr, uid, event_id, ['date', 'date_deadline', \ 'rrule', 'duration', 'exdate']) @@ -1459,15 +1463,15 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ 'edit_all': False, 'recurrency' : False, }) - + new_id = self.copy(cr, uid, real_event_id, default=data, context=context) - + date_new = event_id.split('-')[1] date_new = time.strftime("%Y%m%dT%H%M%S", \ time.strptime(date_new, "%Y%m%d%H%M%S")) exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new res = self.write(cr, uid, [real_event_id], {'exdate': exdate}) - + context.update({'active_id': new_id, 'active_ids': [new_id]}) continue if not real_event_id in new_ids: @@ -1590,10 +1594,10 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\ res = False for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context): event_id = event_datas['id'] - + if self.get_edit_all(cr, uid, event_id, vals=None): event_id = base_calendar_id2real_id(event_id) - + if isinstance(event_id, (int, long)): res = super(calendar_event, self).unlink(cr, uid, event_id, context=context) self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name) @@ -1711,7 +1715,7 @@ class calendar_todo(osv.osv): @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...]. @param context: A standard dictionary for contextual values """ - + assert name == 'date' return self.write(cr, uid, id, { 'date_start': value }, context=context) @@ -1743,9 +1747,9 @@ class ir_attachment(osv.osv): for arg in args: args1.append(map(lambda x:str(x).split('-')[0], arg)) return super(ir_attachment, self).search_count(cr, user, args1, context) - - - + + + def create(self, cr, uid, vals, context=None): if context: id = context.get('default_res_id', False) diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index db69144b408..338870f5006 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -56,7 +56,7 @@ Create dashboard for CRM that includes: 'base', 'base_action_rule', 'process', - 'mail_gateway', + 'email', 'base_calendar', 'resource', 'board' @@ -83,7 +83,7 @@ Create dashboard for CRM that includes: 'wizard/crm_opportunity_to_phonecall_view.xml', 'wizard/crm_partner_to_opportunity_view.xml', - 'wizard/crm_send_email_view.xml', +# 'wizard/crm_send_email_view.xml', 'wizard/crm_add_note_view.xml', 'wizard/crm_merge_opportunities_view.xml', diff --git a/addons/crm/crm.py b/addons/crm/crm.py index f25e4c1a0bf..63344aeca48 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -452,6 +452,7 @@ class crm_case(object): @param ids: List of case's IDs to remind @param context: A standard dictionary for contextual values """ + email_message_obj = self.pool.get('email.message') for case in self.browse(cr, uid, ids, context=context): if not destination and not case.email_from: raise osv.except_osv(_('Error!'), ("Partner Email is not specified in Case")) @@ -486,18 +487,18 @@ class crm_case(object): attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send) # Send an email - subject = "Reminder: [%s] %s" % (str(case.id), case.name,) - tools.email_send( - src, - [dest], - subject, - body, - reply_to=case.section_id.reply_to or '', - openobject_id=str(case.id), - attach=attach_to_send - ) - self._history(cr, uid, [case], _('Send'), history=True, subject=subject, email=dest, details=body, email_from=src) - + subject = "Reminder: [%s] %s" % (str(case.id), case.name, ) + email_message_obj.email_send(cr, uid, + src, + [dest], + subject, + body, + model='crm.case', + reply_to=case.section_id.reply_to, + openobject_id=str(case.id), + attach=attach_to_send + ) + self._history(cr, uid, [case], _('Send'), history=True, subject=subject, email=dest, details=body, email_from=src) return True def _check(self, cr, uid, ids=False, context=None): diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py index 477f9132fab..d877c074020 100644 --- a/addons/crm/crm_action_rule.py +++ b/addons/crm/crm_action_rule.py @@ -37,25 +37,26 @@ class base_action_rule(osv.osv): """ Base Action Rule """ _inherit = 'base.action.rule' _description = 'Action Rules' - + _columns = { - 'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'), - 'trg_max_history': fields.integer('Maximum Communication History'), - 'trg_categ_id': fields.many2one('crm.case.categ', 'Category'), - 'regex_history' : fields.char('Regular Expression on Case History', size=128), - 'act_section_id': fields.many2one('crm.case.section', 'Set Team to'), - 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), + 'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'), + 'trg_max_history': fields.integer('Maximum Communication History'), + 'trg_categ_id': fields.many2one('crm.case.categ', 'Category'), + 'regex_history' : fields.char('Regular Expression on Case History', size=128), + 'act_section_id': fields.many2one('crm.case.section', 'Set Team to'), + 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), 'act_mail_to_partner': fields.boolean('Mail to Partner', help="Check \ -this if you want the rule to send an email to the partner."), +this if you want the rule to send an email to the partner."), } - + def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from', False), context=None): + email_message_obj = self.pool.get('email.message') body = self.format_mail(obj, body) if not emailfrom: if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.address_id and obj.user_id.address_id.email: emailfrom = obj.user_id.address_id.email - + name = '[%d] %s' % (obj.id, tools.ustr(obj.name)) emailfrom = tools.ustr(emailfrom) if hasattr(obj, 'section_id') and obj.section_id and obj.section_id.reply_to: @@ -63,10 +64,10 @@ this if you want the rule to send an email to the partner."), else: reply_to = emailfrom if not emailfrom: - raise osv.except_osv(_('Error!'), + raise osv.except_osv(_('Error!'), _("No E-Mail ID Found for your Company address!")) - return tools.email_send(emailfrom, emails, name, body, reply_to=reply_to, openobject_id=str(obj.id)) - + return email_message_obj.email_send(cr, uid, emailfrom, emails, name, body, model='base.action.rule', reply_to=reply_to, openobject_id=str(obj.id)) + def do_check(self, cr, uid, action, obj, context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, @@ -79,7 +80,7 @@ this if you want the rule to send an email to the partner."), if hasattr(obj, 'categ_id'): ok = ok and (not action.trg_categ_id or action.trg_categ_id.id==obj.categ_id.id) - #Cheking for history + #Cheking for history regex = action.regex_history result_history = True if regex: @@ -109,7 +110,7 @@ this if you want the rule to send an email to the partner."), @param context: A standard dictionary for contextual values """ res = super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context) write = {} - + if hasattr(action, 'act_section_id') and action.act_section_id: obj.section_id = action.act_section_id write['section_id'] = action.act_section_id.id diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 30547807f7e..da9b7bb2418 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -148,7 +148,7 @@ class crm_lead(crm_case, osv.osv): \nIf the case is in progress the state is set to \'Open\'.\ \nWhen the case is over, the state is set to \'Done\'.\ \nIf the case needs to be reviewed then the state is set to \'Pending\'.'), - 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]), + 'message_ids': fields.one2many('email.message', 'res_id', 'Messages', domain=[('model','=',_name)]), } diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 2ea72155dbe..0313595193a 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -105,10 +105,11 @@ - - - Forward to Partner crm.lead.forward.to.partner form form - + new diff --git a/addons/mail_gateway/__init__.py b/addons/email/__init__.py similarity index 92% rename from addons/mail_gateway/__init__.py rename to addons/email/__init__.py index 6526541a068..f038c9f6cab 100644 --- a/addons/mail_gateway/__init__.py +++ b/addons/email/__init__.py @@ -19,8 +19,11 @@ # ############################################################################## -import mail_gateway +import email_smtp_server +import email_message +import email_gateway import res_partner +import wizard # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail_gateway/__openerp__.py b/addons/email/__openerp__.py similarity index 74% rename from addons/mail_gateway/__openerp__.py rename to addons/email/__openerp__.py index e91257fd148..80c5088e181 100644 --- a/addons/mail_gateway/__openerp__.py +++ b/addons/email/__openerp__.py @@ -20,25 +20,28 @@ ############################################################################## { - 'name': 'Email Gateway System', + 'name': 'Email System', 'version': '1.0', 'category': 'Generic Modules/Mail Service', - 'description': """The generic email gateway system allows to send and receive emails - * History for Emails - * Easy Integration with any Module""", + 'description': """The generic email system allows to send and receive emails + * SMTP Server Configuration + * Provide API for Sending Messages + * Store all emails releated messages """, 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', - 'depends': ['base'], + 'depends': ['base', 'base_tools'], 'init_xml': [], 'update_xml': [ - "mail_gateway_view.xml", + "wizard/email_compose_message_view.xml", + "email_view.xml", + "email_gateway_view.xml", "res_partner_view.xml", - 'security/ir.model.access.csv' - + 'security/ir.model.access.csv', + 'email_data.xml', ], 'demo_xml': [], 'installable': True, 'active': False, - 'certificate': '001056784984222247309', + 'certificate': False, } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/email_template/email_template_scheduler_data.xml b/addons/email/email_data.xml similarity index 51% rename from addons/email_template/email_template_scheduler_data.xml rename to addons/email/email_data.xml index 45a9be04ac7..76adcf62810 100644 --- a/addons/email_template/email_template_scheduler_data.xml +++ b/addons/email/email_data.xml @@ -1,14 +1,24 @@ + + Administrator + + + example@test.com + draft + + localhost + + - Email Template scheduler + Email scheduler 1 hours -1 - + diff --git a/addons/mail_gateway/mail_gateway.py b/addons/email/email_gateway.py similarity index 78% rename from addons/mail_gateway/mail_gateway.py rename to addons/email/email_gateway.py index ba6b07e5c54..3de3f04141f 100644 --- a/addons/mail_gateway/mail_gateway.py +++ b/addons/email/email_gateway.py @@ -42,7 +42,7 @@ class mailgate_thread(osv.osv): _description = 'Mailgateway Thread' _columns = { - 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', readonly=True), + 'message_ids': fields.one2many('email.message', 'res_id', 'Messages', readonly=True), } def copy(self, cr, uid, id, default=None, context=None): @@ -97,8 +97,8 @@ class mailgate_thread(osv.osv): @param cr: the current row, from the database cursor, @param uid: the current user’s ID for security checks, @param cases: a browse record list - @param keyword: Subject of the history item - @param history: Value True/False, If True it makes entry in case History otherwise in Case Log + @param keyword: Case action keyword e.g.: If case is closed "Close" keyword is used + @param history: Value True/False, If True it makes entry as a Emails Messages otherwise Log Messages @param email: Email-To / Recipient address @param email_from: Email From / Sender address if any @param email_cc: Comma-Separated list of Carbon Copy Emails To addresse if any @@ -123,15 +123,11 @@ class mailgate_thread(osv.osv): cases = self.browse(cr, uid, cases, context=context) att_obj = self.pool.get('ir.attachment') - obj = self.pool.get('mailgate.message') + obj = self.pool.get('email.message') for case in cases: attachments = [] for att in attach: - att_ids = att_obj.search(cr, uid, [('name','=',att[0]), ('res_id', '=', case.id)]) - if att_ids: - attachments.append(att_ids[0]) - else: attachments.append(att_obj.create(cr, uid, {'res_model':case._name,'res_id':case.id, 'name': att[0], 'datas': base64.encodestring(att[1])})) partner_id = hasattr(case, 'partner_id') and (case.partner_id and case.partner_id.id or False) or False @@ -145,7 +141,7 @@ class mailgate_thread(osv.osv): 'res_id': case.id, 'date': time.strftime('%Y-%m-%d %H:%M:%S'), 'message_id': message_id, - 'description': details, + 'description': details or (hasattr(case, 'description') and case.description or False), 'attachment_ids': [(6, 0, attachments)] } @@ -161,7 +157,7 @@ class mailgate_thread(osv.osv): 'model' : case._name, 'res_id': case.id, 'date': email_date or time.strftime('%Y-%m-%d %H:%M:%S'), - 'description': details or (hasattr(case, 'description') and case.description or False), + 'description': details, 'email_to': email, 'email_from': email_from or \ (hasattr(case, 'user_id') and case.user_id and case.user_id.address_id and \ @@ -171,9 +167,9 @@ class mailgate_thread(osv.osv): 'partner_id': partner_id, 'references': references, 'message_id': message_id, + 'folder': 'inbox', 'attachment_ids': [(6, 0, attachments)] } - obj.create(cr, uid, data, context=context) return True mailgate_thread() @@ -184,116 +180,6 @@ def format_date_tz(date, tz=None): format = tools.DEFAULT_SERVER_DATETIME_FORMAT return tools.server_to_local_timestamp(date, format, format, tz) -class mailgate_message(osv.osv): - ''' - Mailgateway Message - ''' - def open_document(self, cr, uid, ids, context=None): - """ To Open Document - @param self: The object pointer. - @param cr: A database cursor - @param uid: ID of the user currently logged in - @param ids: the ID of messages - @param context: A standard dictionary - """ - action_data = False - if ids: - message_id = ids[0] - mailgate_data = self.browse(cr, uid, message_id, context=context) - model = mailgate_data.model - res_id = mailgate_data.res_id - - action_pool = self.pool.get('ir.actions.act_window') - action_ids = action_pool.search(cr, uid, [('res_model', '=', model)]) - if action_ids: - action_data = action_pool.read(cr, uid, action_ids[0], context=context) - action_data.update({ - 'domain' : "[('id','=',%d)]"%(res_id), - 'nodestroy': True, - 'context': {} - }) - return action_data - - def open_attachment(self, cr, uid, ids, context=None): - """ To Open attachments - @param self: The object pointer. - @param cr: A database cursor - @param uid: ID of the user currently logged in - @param ids: the ID of messages - @param context: A standard dictionary - """ - action_data = False - action_pool = self.pool.get('ir.actions.act_window') - message_pool = self.browse(cr ,uid, ids, context=context)[0] - att_ids = [x.id for x in message_pool.attachment_ids] - action_ids = action_pool.search(cr, uid, [('res_model', '=', 'ir.attachment')]) - if action_ids: - action_data = action_pool.read(cr, uid, action_ids[0], context=context) - action_data.update({ - 'domain': [('id','in',att_ids)], - 'nodestroy': True - }) - return action_data - - def truncate_data(self, cr, uid, data, context=None): - data_list = data and data.split('\n') or [] - if len(data_list) > 3: - res = '\n\t'.join(data_list[:3]) + '...' - else: - res = '\n\t'.join(data_list) - return res - - def _get_display_text(self, cr, uid, ids, name, arg, context=None): - if context is None: - context = {} - tz = context.get('tz') - result = {} - for message in self.browse(cr, uid, ids, context=context): - msg_txt = '' - if message.history: - msg_txt += (message.email_from or '/') + _(' wrote on ') + format_date_tz(message.date, tz) + ':\n\t' - if message.description: - msg_txt += self.truncate_data(cr, uid, message.description, context=context) - else: - msg_txt = (message.user_id.name or '/') + _(' on ') + format_date_tz(message.date, tz) + ':\n\t' - msg_txt += message.name - result[message.id] = msg_txt - return result - - _name = 'mailgate.message' - _description = 'Mailgateway Message' - _order = 'date desc' - _columns = { - 'name':fields.text('Subject', readonly=True), - 'model': fields.char('Object Name', size=128, select=1, readonly=True), - 'res_id': fields.integer('Resource ID', select=1, readonly=True), - 'ref_id': fields.char('Reference Id', size=256, readonly=True, help="Message Id in Email Server.", select=True), - 'date': fields.datetime('Date', readonly=True), - 'history': fields.boolean('Is History?', readonly=True), - 'user_id': fields.many2one('res.users', 'User Responsible', readonly=True), - 'message': fields.text('Description', readonly=True), - 'email_from': fields.char('From', size=128, help="Email From", readonly=True), - 'email_to': fields.char('To', help="Email Recipients", size=256, readonly=True), - 'email_cc': fields.char('Cc', help="Carbon Copy Email Recipients", size=256, readonly=True), - 'email_bcc': fields.char('Bcc', help='Blind Carbon Copy Email Recipients', size=256, readonly=True), - 'message_id': fields.char('Message Id', size=1024, readonly=True, help="Message Id on Email.", select=True), - 'references': fields.text('References', readonly=True, help="References emails."), - 'description': fields.text('Description', readonly=True), - 'partner_id': fields.many2one('res.partner', 'Partner', required=False), - 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments', readonly=True), - 'display_text': fields.function(_get_display_text, method=True, type='text', size="512", string='Display Text'), - } - - def init(self, cr): - cr.execute("""SELECT indexname - FROM pg_indexes - WHERE indexname = 'mailgate_message_res_id_model_idx'""") - if not cr.fetchone(): - cr.execute("""CREATE INDEX mailgate_message_res_id_model_idx - ON mailgate_message (model, res_id)""") - -mailgate_message() - class mailgate_tool(osv.osv_memory): _name = 'email.server.tools' @@ -321,7 +207,7 @@ class mailgate_tool(osv.osv_memory): if isinstance(res_ids, (int, long)): res_ids = [res_ids] - msg_pool = self.pool.get('mailgate.message') + msg_pool = self.pool.get('email.message') for res_id in res_ids: case = self.pool.get(model).browse(cr, uid, res_id, context=context) partner_id = hasattr(case, 'partner_id') and (case.partner_id and case.partner_id.id or False) or False @@ -341,6 +227,7 @@ class mailgate_tool(osv.osv_memory): 'references': msg.get('references') or msg.get('in-reply-to'), 'res_id': res_id, 'user_id': uid, + 'folder': 'inbox', 'attachment_ids': [(6, 0, attach)] } msg_pool.create(cr, uid, msg_data, context=context) diff --git a/addons/email/email_gateway_view.xml b/addons/email/email_gateway_view.xml new file mode 100644 index 00000000000..9fc708eac0f --- /dev/null +++ b/addons/email/email_gateway_view.xml @@ -0,0 +1,90 @@ + + + + + + mailgate.thread.form + mailgate.thread + form + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + mailgate.thread.tree + mailgate.thread + tree + + + + + + + + + + Mailgateway Threads + mailgate.thread + tree,form + form + + + + + + tree + + + + + + form + + + + + + + tree + + + + + + form + + + + +
+
\ No newline at end of file diff --git a/addons/email/email_message.py b/addons/email/email_message.py new file mode 100644 index 00000000000..cf703a78788 --- /dev/null +++ b/addons/email/email_message.py @@ -0,0 +1,402 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL () +# +# 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 +# +############################################################################## + +from osv import osv +from osv import fields +from tools.translate import _ +import tools +import netsvc +import base64 +import time +#import binascii +#import email +#from email.header import decode_header +#from email.utils import parsedate +#import base64 +#import re +#import logging +#import xmlrpclib + +#import re +#import smtplib +#import base64 +#from email import Encoders +#from email.mime.base import MIMEBase +#from email.mime.multipart import MIMEMultipart +#from email.mime.text import MIMEText +#from email.header import decode_header, Header +#from email.utils import formatdate +#import netsvc +#import datetime +#import tools +#import logging +#email_content_types = [ +# 'multipart/mixed', +# 'multipart/alternative', +# 'multipart/related', +# 'text/plain', +# 'text/html' +#] + +LOGGER = netsvc.Logger() +def format_date_tz(date, tz=None): + if not date: + return 'n/a' + format = tools.DEFAULT_SERVER_DATETIME_FORMAT + return tools.server_to_local_timestamp(date, format, format, tz) + +class email_message_template(osv.osv_memory): + _name = 'email.message.template' + _columns = { + 'name':fields.text('Subject', translate=True), + 'model': fields.char('Object Name', size=128, select=1), + 'res_id': fields.integer('Resource ID', select=1), + 'date': fields.datetime('Date'), + 'user_id': fields.many2one('res.users', 'User Responsible'), + 'email_from': fields.char('From', size=128, help="Email From"), + 'email_to': fields.char('To', help="Email Recipients", size=256), + 'email_cc': fields.char('Cc', help="Carbon Copy Email Recipients", size=256), + 'email_bcc': fields.char('Bcc', help='Blind Carbon Copy Email Recipients', size=256), + 'message_id': fields.char('Message Id', size=1024, help="Message Id on Email.", select=1), + 'references': fields.text('References', help="References emails."), + 'reply_to':fields.char('Reply-To', size=250), + 'sub_type': fields.char('Sub Type', size=32), + 'headers': fields.char('x_headers',size=256), + 'priority':fields.integer('Priority'), + 'description': fields.text('Description', translate=True), + 'smtp_server_id':fields.many2one('email.smtp_server', 'SMTP Server'), + } + + _sql_constraints = [] +email_message_template() + +class email_message(osv.osv): + ''' + Email Message + ''' + _inherit = 'email.message.template' + _name = 'email.message' + _description = 'Email Message' + _order = 'date desc' + + def open_document(self, cr, uid, ids, context=None): + """ To Open Document + @param self: The object pointer. + @param cr: A database cursor + @param uid: ID of the user currently logged in + @param ids: the ID of messages + @param context: A standard dictionary + """ + action_data = False + if ids: + message_id = ids[0] + mailgate_data = self.browse(cr, uid, message_id, context=context) + model = mailgate_data.model + res_id = mailgate_data.res_id + + action_pool = self.pool.get('ir.actions.act_window') + action_ids = action_pool.search(cr, uid, [('res_model', '=', model)]) + if action_ids: + action_data = action_pool.read(cr, uid, action_ids[0], context=context) + action_data.update({ + 'domain' : "[('id','=',%d)]"%(res_id), + 'nodestroy': True, + 'context': {} + }) + return action_data + + def open_attachment(self, cr, uid, ids, context=None): + """ To Open attachments + @param self: The object pointer. + @param cr: A database cursor + @param uid: ID of the user currently logged in + @param ids: the ID of messages + @param context: A standard dictionary + """ + action_data = False + action_pool = self.pool.get('ir.actions.act_window') + message_pool = self.browse(cr ,uid, ids, context=context)[0] + att_ids = [x.id for x in message_pool.attachment_ids] + action_ids = action_pool.search(cr, uid, [('res_model', '=', 'ir.attachment')]) + if action_ids: + action_data = action_pool.read(cr, uid, action_ids[0], context=context) + action_data.update({ + 'domain': [('id','in',att_ids)], + 'nodestroy': True + }) + return action_data + + def truncate_data(self, cr, uid, data, context=None): + data_list = data and data.split('\n') or [] + if len(data_list) > 3: + res = '\n\t'.join(data_list[:3]) + '...' + else: + res = '\n\t'.join(data_list) + return res + + def _get_display_text(self, cr, uid, ids, name, arg, context=None): + if context is None: + context = {} + tz = context.get('tz') + result = {} + for message in self.browse(cr, uid, ids, context=context): + msg_txt = '' + if message.history: + msg_txt += (message.email_from or '/') + _(' wrote on ') + format_date_tz(message.date, tz) + ':\n\t' + if message.description: + msg_txt += self.truncate_data(cr, uid, message.description, context=context) + else: + msg_txt = (message.user_id.name or '/') + _(' on ') + format_date_tz(message.date, tz) + ':\n\t' + msg_txt += message.name + result[message.id] = msg_txt + return result + + _columns = { + 'message': fields.text('Description'), + 'partner_id': fields.many2one('res.partner', 'Partner'), + 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'), + 'display_text': fields.function(_get_display_text, method=True, type='text', size="512", string='Display Text'), + 'debug':fields.boolean('Debug', readonly=True), + 'history': fields.boolean('History', readonly=True), + 'folder':fields.selection([ + ('drafts', 'Drafts'), + ('inbox', 'Inbox'), + ('outbox', 'Outbox'), + ('trash', 'Trash'), + ('sent', 'Sent Items'), + ], 'Folder'), + 'state':fields.selection([ + ('draft', 'Draft'), + ('sending', 'Sending'), + ('waiting', 'Waiting'), + ('sent', 'Sent'), + ('exception', 'Exception'), + ], 'State', readonly=True), + } + + _defaults = { + 'state': lambda * a: 'draft', + 'folder': lambda * a: 'outbox', + } + + def unlink(self, cr, uid, ids, context=None): + """ + It just changes the folder of the item to "Trash", if it is no in Trash folder yet, + or completely deletes it if it is already in Trash. + """ + to_update = [] + to_remove = [] + for mail in self.browse(cr, uid, ids, context=context): + if mail.folder == 'trash': + to_remove.append(mail.id) + else: + to_update.append(mail.id) + # Changes the folder to trash + self.write(cr, uid, to_update, {'folder': 'trash'}, context=context) + return super(email_message, self).unlink(cr, uid, to_remove, context=context) + + def init(self, cr): + cr.execute("""SELECT indexname + FROM pg_indexes + WHERE indexname = 'email_message_res_id_model_idx'""") + if not cr.fetchone(): + cr.execute("""CREATE INDEX email_message_res_id_model_idx + ON email_message (model, res_id)""") + + def process_queue(self, cr, uid, ids, arg): + self.process_email_queue(cr, uid, ids=ids) + return True + + def run_mail_scheduler(self, cursor, user, context=None): + """ + This method is called by OpenERP Scheduler + to periodically send emails + """ + try: + self.process_email_queue(cursor, user, context=context) + except Exception, e: + LOGGER.notifyChannel( + "Email Template", + netsvc.LOG_ERROR, + _("Error sending mail: %s") % e) + + def email_send(self, cr, uid, email_from, email_to, subject, body, model=False, email_cc=None, email_bcc=None, reply_to=False, attach=None, + message_id=False, references=False, openobject_id=False, debug=False, subtype='plain', x_headers={}, priority='3', smtp_server_id=False, context=None): + attachment_obj = self.pool.get('ir.attachment') + if email_to and type(email_to) != list: + email_to = [email_to] + if email_cc and type(email_cc) != list: + email_cc = [email_cc] + if email_bcc and type(email_bcc) != list: + email_bcc = [email_bcc] + + msg_vals = { + 'name': subject, + 'model': model or '', + 'date': time.strftime('%Y-%m-%d'), + 'user_id': uid, + 'description': body, + '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 '', + 'reply_to': reply_to, + 'res_id': openobject_id, + 'message_id': message_id, + 'references': references or '', + 'sub_type': subtype or '', + 'headers': x_headers or False, + 'priority': priority, + 'debug': debug, + 'folder': 'outbox', + 'history': True, + 'smtp_server_id': smtp_server_id, + 'state': 'waiting', + } + email_msg_id = self.create(cr, uid, msg_vals, context) + if attach: + attachment_ids = [] + for attachment in attach: + attachment_data = { + 'name': (subject or '') + _(' (Email Attachment)'), + 'datas': attachment[1], + 'datas_fname': attachment[0], + 'description': subject or _('No Description'), + 'res_model':'email.message', + 'res_id': email_msg_id, + } + attachment_ids.append(attachment_obj.create(cr, uid, attachment_data, context)) + self.write(cr, uid, email_msg_id, + { 'attachment_ids': [[6, 0, attachment_ids]] }, context) + return email_msg_id + + def process_retry(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state':'waiting'}, context) + + def process_email_queue(self, cr, uid, ids=None, context=None): + if ids is None: + ids = [] + if context is None: + context = {} + attachment_obj = self.pool.get('ir.attachment') + smtp_server_obj = self.pool.get('email.smtp_server') + if not ids: + filters = [('folder', '=', 'outbox'), ('state', '=', 'waiting')] + if 'filters' in context: + filters.extend(context['filters']) + ids = self.search(cr, uid, filters, context=context) + self.write(cr, uid, ids, {'state':'sending', 'folder':'sent'}, context) + for message in self.browse(cr, uid, ids, context): + try: + attachments = [] + for attach in message.attachment_ids: + attachments.append((attach.datas_fname ,base64.b64decode(attach.datas))) + smtp_server = message.smtp_server_id + if not smtp_server: + smtp_ids = smtp_server_obj.search(cr, uid, [('default','=',True)]) + if smtp_ids: + smtp_server = smtp_server_obj.browse(cr, uid, smtp_ids, context)[0] + res = tools.email_send(message.email_from, + message.email_to and message.email_to.split(',') or [], + message.name, message.description, + email_cc=message.email_cc and message.email_cc.split(',') or [], + email_bcc=message.email_bcc and message.email_bcc.split(',') or [], + reply_to=message.reply_to, + attach=attachments, message_id=message.message_id, references = message.references, + openobject_id=message.res_id, + subtype=message.sub_type, + x_headers=message.headers and eval(message.headers) or {}, + priority=message.priority, debug=message.debug, + smtp_server=smtp_server and smtp_server.smtpserver or None, + smtp_port=smtp_server and smtp_server.smtpport or None, + ssl=smtp_server and smtp_server.smtpssl or False, + smtp_user=smtp_server and smtp_server.smtpuname or None, + smtp_password=smtp_server and smtp_server.smtppass or None) + if res: + self.write(cr, uid, [message.id], {'state':'sent', 'message_id': res}, context) + else: + self.write(cr, uid, [message.id], {'state':'exception'}, context) + except Exception, error: + logger = netsvc.Logger() + logger.notifyChannel("email-template", netsvc.LOG_ERROR, _("Sending of Mail %s failed. Probable Reason:Could not login to server\nError: %s") % (message.id, error)) + self.write(cr, uid, [message.id], {'state':'exception'}, context) + return ids + +# OLD Code. +# def send_all_mail(self, cr, uid, ids=None, context=None): +# if ids is None: +# ids = [] +# if context is None: +# context = {} +# filters = [('folder', '=', 'outbox'), ('state', '!=', 'sending')] +# if 'filters' in context.keys(): +# for each_filter in context['filters']: +# filters.append(each_filter) +# ids = self.search(cr, uid, filters, context=context) +# self.write(cr, uid, ids, {'state':'sending'}, context) +# self.send_this_mail(cr, uid, ids, context) +# return True +# +# def send_this_mail(self, cr, uid, ids=None, context=None): +# #previous method to send email (link with email account can be found at the revision 4172 and below +# result = True +# attachment_pool = self.pool.get('ir.attachment') +# for id in (ids or []): +# try: +# account_obj = self.pool.get('email.smtp_server') +# values = self.read(cr, uid, id, [], context) +# payload = {} +# if values['attachments_ids']: +# for attid in values['attachments_ids']: +# attachment = attachment_pool.browse(cr, uid, attid, context)#,['datas_fname','datas']) +# payload[attachment.datas_fname] = attachment.datas +# result = account_obj.send_email(cr, uid, +# [values['account_id'][0]], +# {'To':values.get('email_to') or u'', +# 'CC':values.get('email_cc') or u'', +# 'BCC':values.get('email_bcc') or u'', +# 'Reply-To':values.get('reply_to') or u''}, +# values['subject'] or u'', +# {'text':values.get('body_text') or u'', 'html':values.get('body_html') or u''}, +# payload=payload, +# message_id=values['message_id'], +# context=context) +# if result == True: +# account = account_obj.browse(cr, uid, values['account_id'][0], context=context) +# if account.auto_delete: +# self.write(cr, uid, id, {'folder': 'trash'}, context=context) +# self.unlink(cr, uid, [id], context=context) +# # Remove attachments for this mail +# attachment_pool.unlink(cr, uid, values['attachments_ids'], context=context) +# else: +# self.write(cr, uid, id, {'folder':'sent', 'state':'na', 'date_mail':time.strftime("%Y-%m-%d %H:%M:%S")}, context) +# else: +# error = result['error_msg'] +# +# except Exception, error: +# logger = netsvc.Logger() +# logger.notifyChannel("email-template", netsvc.LOG_ERROR, _("Sending of Mail %s failed. Probable Reason:Could not login to server\nError: %s") % (id, error)) +# self.write(cr, uid, id, {'state':'na'}, context) +# return result + +email_message() + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/email/email_smtp_server.py b/addons/email/email_smtp_server.py new file mode 100644 index 00000000000..2af9030ca04 --- /dev/null +++ b/addons/email/email_smtp_server.py @@ -0,0 +1,376 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2011 Tiny SPRL () +# +# 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 +# +############################################################################## + +from osv import osv +from osv import fields +from tools.translate import _ +import tools + +#import time +#import binascii +#import email +#from email.header import decode_header +#from email.utils import parsedate +#import base64 +#import re +#import logging +#import xmlrpclib + +#_logger = logging.getLogger('mailgate') + +#import re +import smtplib +#import base64 +#from email import Encoders +#from email.mime.base import MIMEBase +#from email.mime.multipart import MIMEMultipart +#from email.mime.text import MIMEText +#from email.header import decode_header, Header +#from email.utils import formatdate +#import netsvc +#import datetime +#import tools +#import logging + +#EMAIL_PATTERN = re.compile(r'([^()\[\] ,<:\\>@";]+@[^()\[\] ,<:\\>@";]+)') # See RFC822 +#def extract_emails(emails_str): +# """ +# Returns a list of email addresses recognized in a string, ignoring the rest of the string. +# extract_emails('a@b.com,c@bcom, "John Doe" , e@b.com') -> ['a@b.com','c@bcom', 'd@b.com', 'e@b.com']" +# """ +# return EMAIL_PATTERN.findall(emails_str) +# +# +#def extract_emails_from_dict(addresses={}): +# """ +# Extracts email addresses from a dictionary with comma-separated address string values, handling +# separately the To, CC, BCC and Reply-To addresses. +# +# :param addresses: a dictionary of addresses in the form {'To': 'a@b.com,c@bcom; d@b.com;e@b.com' , 'CC': 'e@b.com;f@b.com', ... } +# :return: a dictionary with a list of separate addresses for each header (To, CC, BCC), with an additional key 'all-recipients' +# containing all addresses for the 'To', 'CC', 'BCC' entries. +# """ +# result = {'all-recipients':[]} +# keys = ['To', 'CC', 'BCC', 'Reply-To'] +# for each in keys: +# emails = extract_emails(addresses.get(each, u'')) +# while u'' in emails: +# emails.remove(u'') +# result[each] = emails +# if each != 'Reply-To': +# result['all-recipients'].extend(emails) +# return result + +class email_smtp_server(osv.osv): + """ + SMTP Server + """ + _name = "email.smtp_server" + + _columns = { + 'name': fields.char('Name', + size=64, required=True, + select=True, + help="The Name is used as the Sender name along with the provided From Email, \ +unless it is already specified in the From Email, e.g: John Doe ", + ), + 'email_id': fields.char('From Email', + size=120, required=True, + help="eg: 'john@doe.com' or 'John Doe '"), + 'smtpserver': fields.char('Server', + size=120, required=True, + help="Enter name of outgoing server, eg: smtp.yourdomain.com"), + 'smtpport': fields.integer('SMTP Port', + size=64, required=True, + help="Enter port number, eg: 25 or 587"), + 'smtpuname': fields.char('User Name', + size=120, required=False, + help="Specify the username if your SMTP server requires authentication, " + "otherwise leave it empty."), + 'smtppass': fields.char('Password', + size=120, + required=False), + 'smtptls':fields.boolean('TLS'), + 'smtpssl':fields.boolean('SSL/TLS'), + 'default': fields.boolean('Default', help="Only one account can be default at a time"), + } + + _defaults = { + 'name':lambda self, cursor, user, context:self.pool.get( 'res.users' + ).read(cursor, user, user, ['name'], context)['name'], + 'smtpport': tools.config.get('smtp_port',25), + 'smtpserver': tools.config.get('smtp_server','localhost'), + 'smtpssl': tools.config.get('smtp_ssl',False), + 'smtptls': True, + } + + _sql_constraints = [ + ( + 'email_uniq', + 'unique (email_id)', + 'Another setting already exists with this email ID !') + ] + + def _constraint_unique(self, cr, uid, ids, context=None): + default_ids = self.search(cr, uid, [('default','=',True)]) + if len(default_ids) > 1: + return False + + return True + + _constraints = [ + (_constraint_unique, + 'Error: You must be define one default smtp server account !.', + []) + ] + + def name_get(self, cr, uid, ids, context=None): + return [(a["id"], "%s (%s)" % (a['email_id'], a['name'])) for a in self.read(cr, uid, ids, ['name', 'email_id'], context=context)] + + + + def test_smtp_connection(self, cr, uid, ids, context=None): + """ + Test SMTP connection works + """ + try: + for smtp_server in self.browse(cr, uid, ids, context=context): + smtp = tools.connect_smtp_server(smtp_server.smtpserver, smtp_server.smtpport, user_name=smtp_server.smtpuname, + user_password=smtp_server.smtppass, ssl=smtp_server.smtpssl, tls=smtp_server.smtptls) + try: + smtp.quit() + except Exception: + # ignored, just a consequence of the previous exception + pass + except Exception, error: + raise osv.except_osv( + _("SMTP Connection: Test failed"), + _("Reason: %s") % error + ) + + raise osv.except_osv(_("SMTP Connection: Test Successfully!"), '') + +# def do_approval(self, cr, uid, ids, context=None): +# #TODO: Check if user has rights +# self.write(cr, uid, ids, {'state':'approved'}, context=context) +## wf_service = netsvc.LocalService("workflow") +# +# def smtp_connection(self, cursor, user, id, context=None): +# """ +# This method should now wrap smtp_connection +# """ +# #This function returns a SMTP server object +# logger = netsvc.Logger() +# core_obj = self.browse(cursor, user, id, context=context) +# if core_obj.smtpserver and core_obj.smtpport and core_obj.state == 'approved': +# try: +# serv = self.get_outgoing_server(cursor, user, id, context) +# except Exception, error: +# logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed on login. Probable Reason:Could not login to server\nError: %s") % (id, error)) +# return False +# #Everything is complete, now return the connection +# return serv +# else: +# logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id) +# return False +# +##**************************** MAIL SENDING FEATURES ***********************# +# def send_email(self, cr, uid, ids, addresses, subject='', body=None, payload=None, message_id=None, context=None): +# #TODO: Replace all this with a single email object +# if body is None: +# body = {} +# if payload is None: +# payload = {} +# if context is None: +# context = {} +# logger = netsvc.Logger() +# for id in ids: +# core_obj = self.browse(cr, uid, id, context) +# serv = self.smtp_connection(cr, uid, id) +# if serv: +# try: +# # Need a multipart/mixed wrapper for attachments if content is alternative +# if payload: +# payload_part = MIMEMultipart(_subtype='mixed') +# text_part = MIMEMultipart(_subtype='mixed') +# payload_part.attach(text_part) +# else: +# # otherwise a single multipart/mixed will do the whole job +# payload_part = text_part = MIMEMultipart(_subtype='mixed') +# +# if subject: +# payload_part['Subject'] = subject +# from_email = core_obj.email_id +# if '<' in from_email: +# # We have a structured email address, keep it untouched +# payload_part['From'] = Header(core_obj.email_id, 'utf-8').encode() +# else: +# # Plain email address, construct a structured one based on the name: +# sender_name = Header(core_obj.name, 'utf-8').encode() +# payload_part['From'] = sender_name + " <" + core_obj.email_id + ">" +# payload_part['Organization'] = tools.ustr(core_obj.user.company_id.name) +# payload_part['Date'] = formatdate() +# addresses_l = extract_emails_from_dict(addresses) +# if addresses_l['To']: +# payload_part['To'] = u','.join(addresses_l['To']) +# if addresses_l['CC']: +# payload_part['CC'] = u','.join(addresses_l['CC']) +# if addresses_l['Reply-To']: +# payload_part['Reply-To'] = addresses_l['Reply-To'][0] +# if message_id: +# payload_part['Message-ID'] = message_id +# if body.get('text', False): +# temp_body_text = body.get('text', '') +# l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', '')) +# if l == 0: +# body['text'] = u'No Mail Message' +# # Attach parts into message container. +# # According to RFC 2046, the last part of a multipart message, in this case +# # the HTML message, is best and preferred. +## if core_obj.send_pref in ('text', 'mixed', 'alternative'): +## body_text = body.get('text', u'') +## body_text = tools.ustr(body_text) +## text_part.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8')) +## if core_obj.send_pref in ('html', 'mixed', 'alternative'): +# html_body = body.get('html', u'') +# if len(html_body) == 0 or html_body == u'': +# html_body = body.get('text', u'

<Empty Message>

').replace('\n', '
').replace('\r', '
') +# html_body = tools.ustr(html_body) +# text_part.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8')) +# +# #Now add attachments if any, wrapping into a container multipart/mixed if needed +# if payload: +# for file in payload: +# part = MIMEBase('application', "octet-stream") +# part.set_payload(base64.decodestring(payload[file])) +# part.add_header('Content-Disposition', 'attachment; filename="%s"' % file) +# Encoders.encode_base64(part) +# payload_part.attach(part) +# except Exception, error: +# logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:MIME Error\nDescription: %s") % (id, error)) +# return {'error_msg': _("Server Send Error\nDescription: %s")%error} +# try: +# serv.sendmail(payload_part['From'], addresses_l['all-recipients'], payload_part.as_string()) +# except Exception, error: +# logging.getLogger('email_template').error(_("Mail from Account %s failed. Probable Reason: Server Send Error\n Description: %s"), id, error, exc_info=True) +# return {'error_msg': _("Server Send Error\nDescription: %s")%error} +# #The mail sending is complete +# serv.close() +# logger.notifyChannel(_("Email Template"), netsvc.LOG_INFO, _("Mail from Account %s successfully Sent.") % (id)) +# return True +# else: +# logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id) +# return {'nodestroy':True,'error_msg': _("Mail from Account %s failed. Probable Reason:Account not approved")% id} +# +# def extracttime(self, time_as_string): +# """ +# TODO: DOC THis +# """ +# logger = netsvc.Logger() +# #The standard email dates are of format similar to: +# #Thu, 8 Oct 2009 09:35:42 +0200 +# date_as_date = False +# convertor = {'+':1, '-':-1} +# try: +# time_as_string = time_as_string.replace(',', '') +# date_list = time_as_string.split(' ') +# date_temp_str = ' '.join(date_list[1:5]) +# if len(date_list) >= 6: +# sign = convertor.get(date_list[5][0], False) +# else: +# sign = False +# try: +# dt = datetime.datetime.strptime( +# date_temp_str, +# "%d %b %Y %H:%M:%S") +# except: +# try: +# dt = datetime.datetime.strptime( +# date_temp_str, +# "%d %b %Y %H:%M") +# except: +# return False +# if sign: +# try: +# offset = datetime.timedelta( +# hours=sign * int( +# date_list[5][1:3] +# ), +# minutes=sign * int( +# date_list[5][3:5] +# ) +# ) +# except Exception, e2: +# """Looks like UT or GMT, just forget decoding""" +# return False +# else: +# offset = datetime.timedelta(hours=0) +# dt = dt + offset +# date_as_date = dt.strftime('%Y-%m-%d %H:%M:%S') +# except Exception, e: +# logger.notifyChannel( +# _("Email Template"), +# netsvc.LOG_WARNING, +# _( +# "Datetime Extraction failed.Date:%s \ +# \tError:%s") % ( +# time_as_string, +# e) +# ) +# return date_as_date +# +# def send_receive(self, cr, uid, ids, context=None): +# for id in ids: +# ctx = context.copy() +# ctx['filters'] = [('account_id', '=', id)] +# self.pool.get('email.message').send_all_mail(cr, uid, [], context=ctx) +# return True +# +# def decode_header_text(self, text): +# """ Decode internationalized headers RFC2822. +# To, CC, BCC, Subject fields can contain +# text slices with different encodes, like: +# =?iso-8859-1?Q?Enric_Mart=ED?= , +# =?Windows-1252?Q?David_G=F3mez?= +# Sometimes they include extra " character at the beginning/ +# end of the contact name, like: +# "=?iso-8859-1?Q?Enric_Mart=ED?=" +# and decode_header() does not work well, so we use regular +# expressions (?= ? ? ?=) to split the text slices +# """ +# if not text: +# return text +# p = re.compile("(=\?.*?\?.\?.*?\?=)") +# text2 = '' +# try: +# for t2 in p.split(text): +# text2 += ''.join( +# [s.decode( +# t or 'ascii' +# ) for (s, t) in decode_header(t2)] +# ).encode('utf-8') +# except: +# return text +# return text2 + +email_smtp_server() + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/email/email_view.xml b/addons/email/email_view.xml new file mode 100644 index 00000000000..66c749f69ca --- /dev/null +++ b/addons/email/email_view.xml @@ -0,0 +1,231 @@ + + + + + + + + email.message.form + email.message + form + +
+ + + + + + + + + + + + + + + + + + + + + +