[IMP] email: email cleanup
[odoo/odoo.git] / addons / email_template / email_template.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2009 Sharoon Thomas
6 #    Copyright (C) 2010-Today OpenERP SA (<http://www.openerp.com>)
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>
20 #
21 ##############################################################################
22
23 from osv import osv
24 from osv import fields
25 import base64
26 import random
27 import netsvc
28 import logging
29 import re
30 from tools.translate import _
31 import tools
32 import pooler
33
34 class email_template(osv.osv):
35     "Templates for sending Email"
36     _inherit = 'email.message.common'
37     _name = "email.template"
38     _description = 'Email Templates for Models'
39
40     def get_template_value(self, cr, uid, message=None, model=None, record_id=None, context=None):
41         import mako_template
42         return mako_template.get_value(cr, uid, message=message, model=model, record_id=record_id, context=context)
43
44     def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
45         "Return Template Object"
46         if context is None:
47             context = {}
48         if not template_id:
49             template_id = context.get('template_id', False)
50         if not template_id:
51             return False
52
53         template = self.browse(cr, uid, int(template_id), context)
54         lang = self.get_template_value(cr, uid, template.lang, template.model, record_id, context)
55         if lang:
56             # Use translated template if necessary
57             ctx = context.copy()
58             ctx['lang'] = lang
59             template = self.browse(cr, uid, template.id, ctx)
60         return template
61
62     def onchange_model_id(self, cr, uid, ids, model_id, context=None):
63         mod_name = False
64         if model_id:
65             mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
66         return {'value':{'model':mod_name}}
67
68     _columns = {
69         'name': fields.char('Name', size=250),
70         'model_id':fields.many2one('ir.model', 'Resource'),
71         'model': fields.related('model_id', 'model', string='Model', type="char", size=128, store=True, readonly=True),
72         'track_campaign_item':fields.boolean('Resource Tracking',
73                                 help="Enable this is you wish to include a special \
74 tracking marker in outgoing emails so you can identify replies and link \
75 them back to the corresponding resource record. \
76 This is useful for CRM leads for example"),
77         'lang':fields.char(
78                    'Language',
79                    size=250,
80                    help="The default language for the email."
81                    " Placeholders can be used here. "
82                    "eg. ${object.partner_id.lang}"),
83         'subject':fields.char(
84                   'Subject',
85                   size=200,
86                   help="The subject of email."
87                   " Placeholders can be used here.",
88                   translate=True),
89 #        'description':fields.text(
90 #                    'Standard Body (Text)',
91 #                    help="The text version of the mail",
92 #                    translate=True),
93 #        'body_html':fields.text(
94 #                    'Body (Text-Web Client Only)',
95 #                    help="The text version of the mail",
96 #                    translate=True),
97         'user_signature':fields.boolean(
98                   'Signature',
99                   help="the signature from the User details"
100                   " will be appended to the mail"),
101         'report_name':fields.char(
102                 'Report Filename',
103                 size=200,
104                 help="Name of the generated report file. Placeholders can be used in the filename. eg: 2009_SO003.pdf",
105                 translate=True),
106         'report_template':fields.many2one(
107                   'ir.actions.report.xml',
108                   'Report to send'),
109         'attachment_ids': fields.many2many(
110                     'ir.attachment',
111                     'email_template_attachment_rel',
112                     'email_template_id',
113                     'attachment_id',
114                     'Attached Files',
115                     help="You may attach existing files to this template, "
116                          "so they will be added in all emails created from this template"),
117         'ref_ir_act_window':fields.many2one(
118                     'ir.actions.act_window',
119                     'Window Action',
120                     help="Action that will open this email template on Resource records",
121                     readonly=True),
122         'ref_ir_value':fields.many2one(
123                    'ir.values',
124                    'Wizard Button',
125                    help="Button in the side bar of the form view of this Resource that will invoke the Window Action",
126                    readonly=True),
127         'model_object_field':fields.many2one(
128                  'ir.model.fields',
129                  string="Field",
130                  help="Select the field from the model you want to use."
131                  "\nIf it is a relationship field you will be able to "
132                  "choose the nested values in the box below\n(Note:If "
133                  "there are no values make sure you have selected the"
134                  " correct model)"),
135         'sub_object':fields.many2one(
136                  'ir.model',
137                  'Sub-model',
138                  help='When a relation field is used this field'
139                  ' will show you the type of field you have selected'),
140         'sub_model_object_field':fields.many2one(
141                  'ir.model.fields',
142                  'Sub Field',
143                  help="When you choose relationship fields "
144                  "this field will specify the sub value you can use."),
145         'null_value':fields.char(
146                  'Null Value',
147                  help="This Value is used if the field is empty",
148                  size=50),
149         'copyvalue':fields.char(
150                 'Expression',
151                 size=100,
152                 help="Copy and paste the value in the "
153                 "location you want to use a system value."),
154         'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"),
155         'model': fields.related('model_id','model', type='char', size=128, string='Object'),
156     }
157
158     _sql_constraints = [
159         ('name', 'unique (name)','The template name must be unique !')
160     ]
161
162     def create_action(self, cr, uid, ids, context=None):
163         vals = {}
164         if context is None:
165             context = {}
166         action_obj = self.pool.get('ir.actions.act_window')
167         data_obj = self.pool.get('ir.model.data')
168         for template in self.browse(cr, uid, ids, context=context):
169             src_obj = template.model_id.model
170             model_data_id = data_obj._get_id(cr, uid, 'email', 'email_compose_message_wizard_form')
171             res_id = data_obj.browse(cr, uid, model_data_id, context=context).res_id
172             vals['ref_ir_act_window'] = action_obj.create(cr, uid, {
173                  'name': template.name,
174                  'type': 'ir.actions.act_window',
175                  'res_model': 'email.compose.message',
176                  'src_model': src_obj,
177                  'view_type': 'form',
178                  'context': "{'email_model':'%s', 'email_res_id': active_id,'template_id':'%d','src_rec_id':active_id,'src_rec_ids':active_ids}" % (src_obj, template.id),
179                  'view_mode':'form,tree',
180                  'view_id': res_id,
181                  'target': 'new',
182                  'auto_refresh':1
183             }, context)
184             vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, {
185                  'name': _('Send Mail (%s)') % template.name,
186                  'model': src_obj,
187                  'key2': 'client_action_multi',
188                  'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
189                  'object': True,
190              }, context)
191         self.write(cr, uid, ids, {
192                     'ref_ir_act_window': vals.get('ref_ir_act_window',False),
193                     'ref_ir_value': vals.get('ref_ir_value',False),
194                 }, context)
195         return True
196
197     def unlink_action(self, cr, uid, ids, context=None):
198         for template in self.browse(cr, uid, ids, context=context):
199             try:
200                 if template.ref_ir_act_window:
201                     self.pool.get('ir.actions.act_window').unlink(cr, uid, template.ref_ir_act_window.id, context)
202                 if template.ref_ir_value:
203                     self.pool.get('ir.values').unlink(cr, uid, template.ref_ir_value.id, context)
204             except:
205                 raise osv.except_osv(_("Warning"), _("Deletion of Record failed"))
206
207     def delete_action(self, cr, uid, ids, context=None):
208         self.unlink_action(cr, uid, ids, context=context)
209         return True
210
211     def unlink(self, cr, uid, ids, context=None):
212         self.unlink_action(cr, uid, ids, context=context)
213         return super(email_template, self).unlink(cr, uid, ids, context=context)
214
215     def copy(self, cr, uid, id, default=None, context=None):
216         if default is None:
217             default = {}
218         default = default.copy()
219         old = self.read(cr, uid, id, ['name'], context=context)
220         new_name = _("Copy of template ") + old.get('name', 'No Name')
221         check = self.search(cr, uid, [('name', '=', new_name)], context=context)
222         if check:
223             new_name = new_name + '_' + random.choice('abcdefghij') + random.choice('lmnopqrs') + random.choice('tuvwzyz')
224         default.update({'name':new_name})
225         return super(email_template, self).copy(cr, uid, id, default, context)
226
227     def build_expression(self, field_name, sub_field_name, null_value):
228         """
229         Returns a template expression based on data provided
230         @param field_name: field name
231         @param sub_field_name: sub field name (M2O)
232         @param null_value: default value if the target value is empty
233         @return: computed expression
234         """
235         expression = ''
236         if field_name:
237             expression = "${object." + field_name
238             if sub_field_name:
239                 expression += "." + sub_field_name
240             if null_value:
241                 expression += " or '''%s'''" % null_value
242             expression += "}"
243         return expression
244 #
245 #    def onchange_model_object_field(self, cr, uid, ids, model_object_field, context=None):
246 #        if not model_object_field:
247 #            return {}
248 #        result = {}
249 #        field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
250 #        #Check if field is relational
251 #        if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
252 #            res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
253 #            if res_ids:
254 #                result['sub_object'] = res_ids[0]
255 #                result['copyvalue'] = self.build_expression(False, False, False)
256 #                result['sub_model_object_field'] = False
257 #                result['null_value'] = False
258 #        else:
259 #            #Its a simple field... just compute placeholder
260 #            result['sub_object'] = False
261 #            result['copyvalue'] = self.build_expression(field_obj.name, False, False)
262 #            result['sub_model_object_field'] = False
263 #            result['null_value'] = False
264 #        return {'value':result}
265 #
266 #    def onchange_sub_model_object_field(self, cr, uid, ids, model_object_field, sub_model_object_field, context=None):
267 #        if not model_object_field or not sub_model_object_field:
268 #            return {}
269 #        result = {}
270 #        field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
271 #        if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
272 #            res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
273 #            sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
274 #            if res_ids:
275 #                result['sub_object'] = res_ids[0]
276 #                result['copyvalue'] = self.build_expression(field_obj.name, sub_field_obj.name, False)
277 #                result['sub_model_object_field'] = sub_model_object_field
278 #                result['null_value'] = False
279 #        else:
280 #            #Its a simple field... just compute placeholder
281 #            result['sub_object'] = False
282 #            result['copyvalue'] = self.build_expression(field_obj.name, False, False)
283 #            result['sub_model_object_field'] = False
284 #            result['null_value'] = False
285 #        return {'value':result}
286 #
287 #
288 #    def onchange_null_value(self, cr, uid, ids, model_object_field, sub_model_object_field, null_value, template_language, context=None):
289 #        if not model_object_field and not null_value:
290 #            return {}
291 #        result = {}
292 #        field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
293 #        if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
294 #            res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
295 #            sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
296 #            if res_ids:
297 #                result['sub_object'] = res_ids[0]
298 #                result['copyvalue'] = self.build_expression(field_obj.name,
299 #                                                      sub_field_obj.name,
300 #                                                      null_value,
301 #                                                      template_language
302 #                                                      )
303 #                result['sub_model_object_field'] = sub_model_object_field
304 #                result['null_value'] = null_value
305 #        else:
306 #            #Its a simple field... just compute placeholder
307 #            result['sub_object'] = False
308 #            result['copyvalue'] = self.build_expression(field_obj.name,
309 #                                                  False,
310 #                                                  null_value,
311 #                                                  template_language
312 #                                                  )
313 #            result['sub_model_object_field'] = False
314 #            result['null_value'] = null_value
315 #        return {'value':result}
316
317     def onchange_sub_model_object_value_field(self, cr, uid, ids, model_object_field, sub_model_object_field=False, null_value=None, context=None):
318         result = {
319             'sub_object': False,
320             'copyvalue': False,
321             'sub_model_object_field': False,
322             'null_value': False
323             }
324         if model_object_field:
325             fields_obj = self.pool.get('ir.model.fields')
326             field_value = fields_obj.browse(cr, uid, model_object_field, context)
327             if field_value.ttype in ['many2one', 'one2many', 'many2many']:
328                 res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_value.relation)], context=context)
329                 sub_field_value = False
330                 if sub_model_object_field:
331                     sub_field_value = fields_obj.browse(cr, uid, sub_model_object_field, context)
332                 if res_ids:
333                     result.update({
334                         'sub_object': res_ids[0],
335                         'copyvalue': self.build_expression(field_value.name, sub_field_value and sub_field_value.name or False, null_value or False),
336                         'sub_model_object_field': sub_model_object_field or False,
337                         'null_value': null_value or False
338                         })
339             else:
340                 result.update({
341                         'copyvalue': self.build_expression(field_value.name, False, null_value or False),
342                         'null_value': null_value or False
343                         })
344         return {'value':result}
345
346
347     def _generate_email(self, cr, uid, template_id, record_id, context=None):
348         """
349         Generates an email from the template for
350         record record_id of target object
351         """
352         if context is None:
353             context = {}
354         smtp_pool = self.pool.get('email.smtp_server')
355         email_message_pool = self.pool.get('email.message')
356         report_xml_pool = self.pool.get('ir.actions.report.xml')
357         template = self.get_email_template(cr, uid, template_id, record_id, context)
358         smtp_server_id = context.get('smtp_server_id', False)
359         if not smtp_server_id and template.smtp_server_id:
360             smtp_server_id = template.smtp_server_id.id
361         else:
362             smtp_ids = smtp_pool.search(cr, uid, [('default','=',True)])
363             smtp_server_id = smtp_ids and smtp_ids[0]
364         smtp_server = smtp_pool.browse(cr, uid, smtp_server_id, context=context)
365         # determine name of sender, either it is specified in email_id
366
367         email_id = smtp_server.email_id.strip()
368         email_from = re.findall(r'([^ ,<@]+@[^> ,]+)', email_id)[0]
369         if email_from != email_id:
370             email_from = smtp_server.email_id
371         else:
372             email_from = tools.ustr(smtp_server.name) + "<" + tools.ustr(email_id) + ">"
373
374         model = template.model_id.model
375         values = {
376             'email_from': email_from,
377             'email_to': self.get_template_value(cr, uid, template.email_to, model, record_id, context),
378             'email_cc': self.get_template_value(cr, uid, template.email_cc, model, record_id, context),
379             'email_bcc': self.get_template_value(cr, uid, template.email_bcc, model, record_id, context),
380             'reply_to': self.get_template_value(cr, uid, template.reply_to, model, record_id, context),
381             'name': self.get_template_value(cr, uid, template.subject, model, record_id, context),
382             'description': self.get_template_value(cr, uid, template.description, model, record_id, context),
383             #'body_html': self.get_template_value(cr, uid, template.body_html, model, record_id, context),
384         }
385
386         if template.message_id:
387             # use provided message_id with placeholders
388             values.update({'message_id': self.get_template_value(cr, uid, template.message_id, model, record_id, context)})
389
390         elif template['track_campaign_item']:
391             # get appropriate message-id
392             values.update({'message_id': tools.misc.generate_tracking_message_id(record_id)})
393
394         #Use signatures if allowed
395         if template.user_signature:
396             sign = self.pool.get('res.users').read(cr, uid, uid, ['signature'], context)['signature']
397             if values['description']:
398                 values['description'] += '\n\n' + sign
399             #if values['body_html']:
400             #    values['body_html'] += sign
401
402         attachment = []
403
404         # Add report as a Document
405         if template.report_template:
406             report_name = template.report_name
407             reportname = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
408             data = {}
409             data['model'] = template.model
410
411             # Ensure report is rendered using template's language
412             ctx = context.copy()
413             if template.lang:
414                 ctx['lang'] = self.get_template_value(cr, uid, template.lang, template.model, record_id, context)
415             service = netsvc.LocalService(reportname)
416             (result, format) = service.create(cr, uid, [record_id], data, ctx)
417             result = base64.b64encode(result)
418             if not report_name:
419                 report_name = reportname
420             report_name = report_name + "." + format
421             attachment.append((report_name, result))
422
423
424         # Add document attachments
425         for attach in template.attachment_ids:
426             #attach = attahcment_obj.browse(cr, uid, attachment_id, context)
427             attachment.append((attach.datas_fname, attach.datas))
428
429         #Send emails
430         context.update({'notemplate':True})
431         email_id = email_message_pool.email_send(cr, uid, values.get('email_from'), values.get('email_to'), values.get('name'), values.get('description'),
432                     model=model, email_cc=values.get('email_cc'), email_bcc=values.get('email_bcc'), reply_to=values.get('reply_to'),
433                     attach=attachment, message_id=values.get('message_id'), openobject_id=record_id, debug=True, subtype='plain', x_headers={}, priority='3', smtp_server_id=smtp_server.id, context=context)
434         email_message_pool.write(cr, uid, email_id, {'template_id': context.get('template_id',template.id)})
435         return email_id
436
437
438
439     def generate_email(self, cr, uid, template_id, record_id,  context=None):
440         if context is None:
441             context = {}
442         email_id = self._generate_email(cr, uid, template_id, record_id, context)
443         return email_id
444
445 email_template()
446
447
448 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: