7
LOGGER = netsvc.Logger()
11
from osv import osv, fields
12
from tools.translate import _
13
from mako.template import Template #For backward combatibility
15
from mako.template import Template as MakoTemplate
16
from mako import exceptions
17
TEMPLATE_ENGINES.append(('mako', 'Mako Templates'))
22
_("Mako templates not installed")
25
from django.template import Context, Template as DjangoTemplate
27
#http://code.google.com/p/django-tagging/issues/detail?id=110
28
from django.conf import settings
31
TEMPLATE_ENGINES.append(('django', 'Django Template'))
36
_("Django templates not installed")
39
import email_template_engines
45
def get_value(cursor, user, recid, message=None, template=None, context=None):
47
Evaluates an expression and returns its value
48
@param cursor: Database Cursor
49
@param user: ID of current user
50
@param recid: ID of the target record under evaluation
51
@param message: The expression to be evaluated
52
@param template: BrowseRecord object of the current template
53
@param context: Open ERP Context
54
@return: Computed message (unicode) or u""
56
pool = pooler.get_pool(cursor.dbname)
59
#Returns the computed expression
62
message = tools.ustr(message)
63
object = pool.get(template.model_int_name).browse(cursor, user, recid, context)
65
'user':pool.get('res.users').browse(cursor, user, user, context),
68
if template.template_language == 'mako':
69
templ = MakoTemplate(message, input_encoding='utf-8')
70
reply = MakoTemplate(message).render_unicode(object=object,
73
format_exceptions=True)
74
elif template.template_language == 'django':
75
templ = DjangoTemplate(message)
76
env['object'] = object
77
env['peobject'] = object
78
reply = templ.render(Context(env))
85
class email_template(osv.osv):
86
"Templates for sending Email"
88
_name = "email.template"
89
_description = 'Email Templates for Models'
91
def change_model(self, cursor, user, ids, object_name, context=None):
93
mod_name = self.pool.get('ir.model').read(
97
['model'], context)['model']
101
'value':{'model_int_name':mod_name}
105
'name' : fields.char('Name of Template', size=100, required=True),
106
'object_name':fields.many2one('ir.model', 'Model'),
107
'model_int_name':fields.char('Model Internal Name', size=200,),
108
'def_to':fields.char(
111
help="The default recepient of email."
112
"Placeholders can be used here."),
113
'def_cc':fields.char(
116
help="The default CC for the email."
117
" Placeholders can be used here."),
118
'def_bcc':fields.char(
121
help="The default BCC for the email."
122
" Placeholders can be used here."),
126
help="The default language for the email."
127
" Placeholders can be used here. "
128
"eg. ${object.partner_id.lang}"),
129
'def_subject':fields.char(
132
help="The default subject of email."
133
" Placeholders can be used here.",
135
'def_body_text':fields.text(
136
'Standard Body (Text)',
137
help="The text version of the mail",
139
'def_body_html':fields.text(
140
'Body (Text-Web Client Only)',
141
help="The text version of the mail",
143
'use_sign':fields.boolean(
145
help="the signature from the User details"
146
"will be appened to the mail"),
147
'file_name':fields.char(
150
help="File name pattern can be specified with placeholders."
151
"eg. 2009_SO003.pdf",
153
'report_template':fields.many2one(
154
'ir.actions.report.xml',
156
'ref_ir_act_window':fields.many2one(
157
'ir.actions.act_window',
160
'ref_ir_value':fields.many2one(
164
'allowed_groups':fields.many2many(
166
'template_group_rel',
167
'templ_id', 'group_id',
168
string="Allowed User Groups",
169
help="Only users from these groups will be"
170
" allowed to send mails from this Template"),
171
'enforce_from_account':fields.many2one(
172
'email_template.account',
173
string="Enforce From Account",
174
help="Emails will be sent only from this account.",
175
domain="[('company','=','yes')]"),
176
'model_object_field':fields.many2one(
179
help="Select the field from the model you want to use."
180
"\nIf it is a relationship field you will be able to "
181
"choose the nested values in the box below\n(Note:If "
182
"there are no values make sure you have selected the"
185
'sub_object':fields.many2one(
188
help='When a relation field is used this field'
189
' will show you the type of field you have selected',
191
'sub_model_object_field':fields.many2one(
194
help="When you choose relationship fields "
195
"this field will specify the sub value you can use.",
197
'null_value':fields.char(
199
help="This Value is used if the field is empty",
200
size=50, store=False),
201
'copyvalue':fields.char(
204
help="Copy and paste the value in the "
205
"location you want to use a system value.",
207
'table_html':fields.text(
209
help="Copy this html code to your HTML message"
210
" body for displaying the info in your mail.",
212
#Template language(engine eg.Mako) specifics
213
'template_language':fields.selection(
215
'Templating Language',
224
('name', 'unique (name)', _('The template name must be unique !'))
227
def create(self, cr, uid, vals, context=None):
228
id = super(email_template, self).create(cr, uid, vals, context)
229
src_obj = self.pool.get('ir.model').read(cr, uid, vals['object_name'], ['model'], context)['model']
230
vals['ref_ir_act_window'] = self.pool.get('ir.actions.act_window').create(cr, uid, {
231
'name': _("%s Mail Form") % vals['name'],
232
'type': 'ir.actions.act_window',
233
'res_model': 'email_template.send.wizard',
234
'src_model': src_obj,
236
'context': "{'src_model':'%s','template_id':'%d','src_rec_id':active_id,'src_rec_ids':active_ids}" % (src_obj, id),
237
'view_mode':'form,tree',
238
'view_id': self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'email_template.send.wizard.form')], context=context)[0],
242
vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, {
243
'name': _('Send Mail (%s)') % vals['name'],
245
'key2': 'client_action_multi',
246
'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
249
self.write(cr, uid, id, {
250
'ref_ir_act_window': vals['ref_ir_act_window'],
251
'ref_ir_value': vals['ref_ir_value'],
255
def unlink(self, cr, uid, ids, context=None):
256
for template in self.browse(cr, uid, ids, context):
257
obj = self.pool.get(template.object_name.model)
259
if template.ref_ir_act_window:
260
self.pool.get('ir.actions.act_window').unlink(cr, uid, template.ref_ir_act_window.id, context)
261
if template.ref_ir_value:
262
self.pool.get('ir.values').unlink(cr, uid, template.ref_ir_value.id, context)
264
raise osv.except_osv(_("Warning"), _("Deletion of Record failed"))
265
return super(email_template, self).unlink(cr, uid, ids, context)
267
def copy(self, cr, uid, id, default=None, context=None):
270
default = default.copy()
271
old = self.read(cr, uid, id, ['name'], context=context)
272
new_name = _("Copy of template ") + old.get('name', 'No Name')
273
check = self.search(cr, uid, [('name', '=', new_name)], context=context)
275
new_name = new_name + '_' + random.choice('abcdefghij') + random.choice('lmnopqrs') + random.choice('tuvwzyz')
276
default.update({'name':new_name})
277
return super(email_template, self).copy(cr, uid, id, default, context)
281
sub_model_object_field,
282
null_value, template_language='mako'):
284
Returns the expression based on data provided
285
@param model_object_field: First level field
286
@param sub_model_object_field: Second level drilled down field (M2O)
287
@param null_value: What has to be returned if the value is empty
288
@param template_language: The language used for templating
289
@return: computed expression
293
if template_language == 'mako':
294
if model_object_field:
295
copy_val = "${object." + model_object_field
296
if sub_model_object_field:
297
copy_val += "." + sub_model_object_field
299
copy_val += " or '" + null_value + "'"
300
if model_object_field:
302
elif template_language == 'django':
303
if model_object_field:
304
copy_val = "{{object." + model_object_field
305
if sub_model_object_field:
306
copy_val += "." + sub_model_object_field
308
copy_val = copy_val + '|default:"' + null_value + '"'
309
copy_val = copy_val + "}}"
312
def onchange_model_object_field(self, cr, uid, ids, model_object_field, template_language, context=None):
313
if not model_object_field:
316
field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
317
#Check if field is relational
318
if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
319
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
321
result['sub_object'] = res_ids[0]
322
result['copyvalue'] = self.compute_pl(False,
326
result['sub_model_object_field'] = False
327
result['null_value'] = False
329
#Its a simple field... just compute placeholder
330
result['sub_object'] = False
331
result['copyvalue'] = self.compute_pl(field_obj.name,
336
result['sub_model_object_field'] = False
337
result['null_value'] = False
338
return {'value':result}
340
def onchange_sub_model_object_field(self, cr, uid, ids, model_object_field, sub_model_object_field, template_language, context=None):
341
if not model_object_field or not sub_model_object_field:
344
field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
345
if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
346
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
347
sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
349
result['sub_object'] = res_ids[0]
350
result['copyvalue'] = self.compute_pl(field_obj.name,
355
result['sub_model_object_field'] = sub_model_object_field
356
result['null_value'] = False
358
#Its a simple field... just compute placeholder
359
result['sub_object'] = False
360
result['copyvalue'] = self.compute_pl(field_obj.name,
365
result['sub_model_object_field'] = False
366
result['null_value'] = False
367
return {'value':result}
369
def onchange_null_value(self, cr, uid, ids, model_object_field, sub_model_object_field, null_value, template_language, context=None):
370
if not model_object_field and not null_value:
373
field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
374
if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
375
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
376
sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
378
result['sub_object'] = res_ids[0]
379
result['copyvalue'] = self.compute_pl(field_obj.name,
384
result['sub_model_object_field'] = sub_model_object_field
385
result['null_value'] = null_value
387
#Its a simple field... just compute placeholder
388
result['sub_object'] = False
389
result['copyvalue'] = self.compute_pl(field_obj.name,
394
result['sub_model_object_field'] = False
395
result['null_value'] = null_value
396
return {'value':result}
398
def generate_attach_reports(self,
406
Generate report to be attached and attach it
409
@param cursor: Database Cursor
410
@param user: ID of User
411
@param template: Browse record of
413
@param record_id: ID of the target model
414
for which this mail has
416
@param mail: Browse record of email object
419
reportname = 'report.' + \
420
self.pool.get('ir.actions.report.xml').read(
423
template.report_template.id,
425
context)['report_name']
426
service = netsvc.LocalService(reportname)
428
data['model'] = template.model_int_name
429
(result, format) = service.create(cursor,
434
attachment_obj = self.pool.get('ir.attachment')
436
'name':mail.subject + ' (Email Attachment)',
437
'datas':base64.b64encode(result),
438
'datas_fname':tools.ustr(
446
) or 'Report') + "." + format,
447
'description':mail.subject or "No Description",
448
'res_model':'email_template.mailbox',
451
attachment_id = attachment_obj.create(cursor,
456
self.pool.get('email_template.mailbox').write(
462
[6, 0, [attachment_id]]
464
'mail_type':'multipart/mixed'
469
def generate_mailbox_item_from_template(self,
476
Generates an email from the template for
477
record record_id of target object
479
@param cursor: Database Cursor
480
@param user: ID of User
481
@param template: Browse record of
483
@param record_id: ID of the target model
484
for which this mail has
486
@return: ID of created object
490
#If account to send from is in context select it, else use enforced account
491
if 'account_id' in context.keys():
492
from_account = self.pool.get('email_template.account').read(
495
context.get('account_id'),
496
['name', 'email_id'],
501
'id':template.enforce_from_account.id,
502
'name':template.enforce_from_account.name,
503
'email_id':template.enforce_from_account.email_id
505
lang = get_value(cursor,
513
ctx.update({'lang':lang})
514
template = self.browse(cursor, user, template_id, context=ctx)
516
'email_from': tools.ustr(from_account['name']) + \
517
"<" + tools.ustr(from_account['email_id']) + ">",
518
'email_to':get_value(cursor,
524
'email_cc':get_value(cursor,
530
'email_bcc':get_value(cursor,
536
'subject':get_value(cursor,
539
template.def_subject,
542
'body_text':get_value(cursor,
545
template.def_body_text,
548
'body_html':get_value(cursor,
551
template.def_body_html,
554
'account_id' :from_account['id'],
555
#This is a mandatory field when automatic emails are sent
558
'mail_type':'multipart/alternative'
560
#Use signatures if allowed
561
if template.use_sign:
562
sign = self.pool.get('res.users').read(cursor,
566
context)['signature']
567
if mailbox_values['body_text']:
568
mailbox_values['body_text'] += sign
569
if mailbox_values['body_html']:
570
mailbox_values['body_html'] += sign
571
mailbox_id = self.pool.get('email_template.mailbox').create(
578
def generate_mail(self,
586
template = self.browse(cursor, user, template_id, context=context)
588
raise Exception("The requested template could not be loaded")
589
for record_id in record_ids:
590
mailbox_id = self._generate_mailbox_item_from_template(
596
mail = self.pool.get('email_template.mailbox').browse(
602
if template.report_template:
603
self._generate_attach_reports(
611
self.pool.get('email_template.mailbox').write(
622
class email_template_preview(osv.osv_memory):
623
_name = "email_template.preview"
624
_description = "Email Template Preview"
626
def _get_model_recs(self, cr, uid, context=None):
629
#Fills up the selection box which allows records from the selected object to be displayed
630
self.context = context
631
if 'active_id' in context.keys():
632
# context['active_id'] = 5
633
ref_obj_id = self.pool.get('email.template').read(cr, uid, context['active_id'], ['object_name'], context)
634
ref_obj_name = self.pool.get('ir.model').read(cr, uid, ref_obj_id[0], ['model'], context)['model']
635
ref_obj_ids = self.pool.get(ref_obj_name).search(cr, uid, [], context=context)
636
ref_obj_recs = self.pool.get(ref_obj_name).name_get(cr, uid, ref_obj_ids, context)
639
def _default_model(self, cursor, user, context=None):
641
Returns the default value for model field
642
@param cursor: Database Cursor
643
@param user: ID of current user
644
@param context: Open ERP Context
646
return self.pool.get('email.template').read(
649
context['active_id'],
651
context)['object_name']
654
'ref_template':fields.many2one(
656
'Template', readonly=True),
657
'rel_model':fields.many2one('ir.model', 'Model', readonly=True),
658
'rel_model_ref':fields.selection(_get_model_recs, 'Referred Document'),
659
'to':fields.char('To', size=250, readonly=True),
660
'cc':fields.char('CC', size=250, readonly=True),
661
'bcc':fields.char('BCC', size=250, readonly=True),
662
'subject':fields.char('Subject', size=200, readonly=True),
663
'body_text':fields.text('Body', readonly=True),
664
'body_html':fields.text('Body', readonly=True),
665
'report':fields.char('Report Name', size=100, readonly=True),
668
'ref_template': lambda self, cr, uid, ctx:ctx['active_id'],
669
'rel_model': _default_model
672
def on_change_ref(self, cr, uid, ids, rel_model_ref, context=None):
675
if not rel_model_ref:
679
context = self.context
680
template = self.pool.get('email.template').browse(cr, uid, context['active_id'], context)
681
#Search translated template
682
lang = get_value(cr, uid, rel_model_ref, template.lang, template, context)
685
ctx.update({'lang':lang})
686
template = self.pool.get('email.template').browse(cr, uid, context['active_id'], ctx)
687
vals['to'] = get_value(cr, uid, rel_model_ref, template.def_to, template, context)
688
vals['cc'] = get_value(cr, uid, rel_model_ref, template.def_cc, template, context)
689
vals['bcc'] = get_value(cr, uid, rel_model_ref, template.def_bcc, template, context)
690
vals['subject'] = get_value(cr, uid, rel_model_ref, template.def_subject, template, context)
691
vals['body_text'] = get_value(cr, uid, rel_model_ref, template.def_body_text, template, context)
692
vals['body_html'] = get_value(cr, uid, rel_model_ref, template.def_body_html, template, context)
693
vals['report'] = get_value(cr, uid, rel_model_ref, template.file_name, template, context)
694
return {'value':vals}
696
email_template_preview()
698
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: