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', size=100, required=True),
106 'object_name':fields.many2one('ir.model', 'Model'),
107 'model_int_name':fields.char('Model Internal Name', size=200,),
108 'enforce_from_account':fields.many2one(
109 'email_template.account',
110 string="Enforce From Account",
111 help="Emails will be sent only from this account."),
112 'from_email' : fields.related('enforce_from_account', 'email_id',
113 type='char', string='From',),
114 'def_to':fields.char(
117 help="The default recepient of email."
118 "Placeholders can be used here."),
119 'def_cc':fields.char(
122 help="The default CC for the email."
123 " Placeholders can be used here."),
124 'def_bcc':fields.char(
127 help="The default BCC for the email."
128 " Placeholders can be used here."),
132 help="The default language for the email."
133 " Placeholders can be used here. "
134 "eg. ${object.partner_id.lang}"),
135 'def_subject':fields.char(
138 help="The default subject of email."
139 " Placeholders can be used here.",
141 'def_body_text':fields.text(
142 'Standard Body (Text)',
143 help="The text version of the mail",
145 'def_body_html':fields.text(
146 'Body (Text-Web Client Only)',
147 help="The text version of the mail",
149 'use_sign':fields.boolean(
151 help="the signature from the User details"
152 "will be appened to the mail"),
153 'file_name':fields.char(
156 help="File name pattern can be specified with placeholders."
157 "eg. 2009_SO003.pdf",
159 'report_template':fields.many2one(
160 'ir.actions.report.xml',
162 'ref_ir_act_window':fields.many2one(
163 'ir.actions.act_window',
166 'ref_ir_value':fields.many2one(
170 'allowed_groups':fields.many2many(
172 'template_group_rel',
173 'templ_id', 'group_id',
174 string="Allowed User Groups",
175 help="Only users from these groups will be"
176 " allowed to send mails from this Template"),
177 'model_object_field':fields.many2one(
180 help="Select the field from the model you want to use."
181 "\nIf it is a relationship field you will be able to "
182 "choose the nested values in the box below\n(Note:If "
183 "there are no values make sure you have selected the"
186 'sub_object':fields.many2one(
189 help='When a relation field is used this field'
190 ' will show you the type of field you have selected',
192 'sub_model_object_field':fields.many2one(
195 help="When you choose relationship fields "
196 "this field will specify the sub value you can use.",
198 'null_value':fields.char(
200 help="This Value is used if the field is empty",
201 size=50, store=False),
202 'copyvalue':fields.char(
205 help="Copy and paste the value in the "
206 "location you want to use a system value.",
208 'table_html':fields.text(
210 help="Copy this html code to your HTML message"
211 " body for displaying the info in your mail.",
213 #Template language(engine eg.Mako) specifics
214 'template_language':fields.selection(
216 'Templating Language',
222 'template_language' : lambda *a:'mako',
226 ('name', 'unique (name)', _('The template name must be unique !'))
229 def create(self, cr, uid, vals, context=None):
230 id = super(email_template, self).create(cr, uid, vals, context)
231 src_obj = self.pool.get('ir.model').read(cr, uid, vals['object_name'], ['model'], context)['model']
232 vals['ref_ir_act_window'] = self.pool.get('ir.actions.act_window').create(cr, uid, {
233 'name': _("%s Mail Form") % vals['name'],
234 'type': 'ir.actions.act_window',
235 'res_model': 'email_template.send.wizard',
236 'src_model': src_obj,
238 'context': "{'src_model':'%s','template_id':'%d','src_rec_id':active_id,'src_rec_ids':active_ids}" % (src_obj, id),
239 'view_mode':'form,tree',
240 'view_id': self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'email_template.send.wizard.form')], context=context)[0],
244 vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, {
245 'name': _('Send Mail (%s)') % vals['name'],
247 'key2': 'client_action_multi',
248 'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
251 self.write(cr, uid, id, {
252 'ref_ir_act_window': vals['ref_ir_act_window'],
253 'ref_ir_value': vals['ref_ir_value'],
257 def unlink(self, cr, uid, ids, context=None):
258 for template in self.browse(cr, uid, ids, context):
259 obj = self.pool.get(template.object_name.model)
261 if template.ref_ir_act_window:
262 self.pool.get('ir.actions.act_window').unlink(cr, uid, template.ref_ir_act_window.id, context)
263 if template.ref_ir_value:
264 self.pool.get('ir.values').unlink(cr, uid, template.ref_ir_value.id, context)
266 raise osv.except_osv(_("Warning"), _("Deletion of Record failed"))
267 return super(email_template, self).unlink(cr, uid, ids, context)
269 def copy(self, cr, uid, id, default=None, context=None):
272 default = default.copy()
273 old = self.read(cr, uid, id, ['name'], context=context)
274 new_name = _("Copy of template ") + old.get('name', 'No Name')
275 check = self.search(cr, uid, [('name', '=', new_name)], context=context)
277 new_name = new_name + '_' + random.choice('abcdefghij') + random.choice('lmnopqrs') + random.choice('tuvwzyz')
278 default.update({'name':new_name})
279 return super(email_template, self).copy(cr, uid, id, default, context)
283 sub_model_object_field,
284 null_value, template_language='mako'):
286 Returns the expression based on data provided
287 @param model_object_field: First level field
288 @param sub_model_object_field: Second level drilled down field (M2O)
289 @param null_value: What has to be returned if the value is empty
290 @param template_language: The language used for templating
291 @return: computed expression
295 if template_language == 'mako':
296 if model_object_field:
297 copy_val = "${object." + model_object_field
298 if sub_model_object_field:
299 copy_val += "." + sub_model_object_field
301 copy_val += " or '" + null_value + "'"
302 if model_object_field:
304 elif template_language == 'django':
305 if model_object_field:
306 copy_val = "{{object." + model_object_field
307 if sub_model_object_field:
308 copy_val += "." + sub_model_object_field
310 copy_val = copy_val + '|default:"' + null_value + '"'
311 copy_val = copy_val + "}}"
314 def onchange_model_object_field(self, cr, uid, ids, model_object_field, template_language, context=None):
315 if not model_object_field:
318 field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
319 #Check if field is relational
320 if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
321 res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
323 result['sub_object'] = res_ids[0]
324 result['copyvalue'] = self.compute_pl(False,
328 result['sub_model_object_field'] = False
329 result['null_value'] = False
331 #Its a simple field... just compute placeholder
332 result['sub_object'] = False
333 result['copyvalue'] = self.compute_pl(field_obj.name,
338 result['sub_model_object_field'] = False
339 result['null_value'] = False
340 return {'value':result}
342 def onchange_sub_model_object_field(self, cr, uid, ids, model_object_field, sub_model_object_field, template_language, context=None):
343 if not model_object_field or not sub_model_object_field:
346 field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
347 if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
348 res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
349 sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
351 result['sub_object'] = res_ids[0]
352 result['copyvalue'] = self.compute_pl(field_obj.name,
357 result['sub_model_object_field'] = sub_model_object_field
358 result['null_value'] = False
360 #Its a simple field... just compute placeholder
361 result['sub_object'] = False
362 result['copyvalue'] = self.compute_pl(field_obj.name,
367 result['sub_model_object_field'] = False
368 result['null_value'] = False
369 return {'value':result}
371 def onchange_null_value(self, cr, uid, ids, model_object_field, sub_model_object_field, null_value, template_language, context=None):
372 if not model_object_field and not null_value:
375 field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
376 if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
377 res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
378 sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
380 result['sub_object'] = res_ids[0]
381 result['copyvalue'] = self.compute_pl(field_obj.name,
386 result['sub_model_object_field'] = sub_model_object_field
387 result['null_value'] = null_value
389 #Its a simple field... just compute placeholder
390 result['sub_object'] = False
391 result['copyvalue'] = self.compute_pl(field_obj.name,
396 result['sub_model_object_field'] = False
397 result['null_value'] = null_value
398 return {'value':result}
400 def generate_attach_reports(self,
408 Generate report to be attached and attach it
411 @param cursor: Database Cursor
412 @param user: ID of User
413 @param template: Browse record of
415 @param record_id: ID of the target model
416 for which this mail has
418 @param mail: Browse record of email object
421 reportname = 'report.' + \
422 self.pool.get('ir.actions.report.xml').read(
425 template.report_template.id,
427 context)['report_name']
428 service = netsvc.LocalService(reportname)
430 data['model'] = template.model_int_name
431 (result, format) = service.create(cursor,
436 attachment_obj = self.pool.get('ir.attachment')
438 'name':mail.subject + ' (Email Attachment)',
439 'datas':base64.b64encode(result),
440 'datas_fname':tools.ustr(
448 ) or 'Report') + "." + format,
449 'description':mail.subject or "No Description",
450 'res_model':'email_template.mailbox',
453 attachment_id = attachment_obj.create(cursor,
458 self.pool.get('email_template.mailbox').write(
464 [6, 0, [attachment_id]]
466 'mail_type':'multipart/mixed'
471 def generate_mailbox_item_from_template(self,
478 Generates an email from the template for
479 record record_id of target object
481 @param cursor: Database Cursor
482 @param user: ID of User
483 @param template: Browse record of
485 @param record_id: ID of the target model
486 for which this mail has
488 @return: ID of created object
492 #If account to send from is in context select it, else use enforced account
493 if 'account_id' in context.keys():
494 from_account = self.pool.get('email_template.account').read(
497 context.get('account_id'),
498 ['name', 'email_id'],
503 'id':template.enforce_from_account.id,
504 'name':template.enforce_from_account.name,
505 'email_id':template.enforce_from_account.email_id
507 lang = get_value(cursor,
515 ctx.update({'lang':lang})
516 template = self.browse(cursor, user, template_id, context=ctx)
518 'email_from': tools.ustr(from_account['name']) + \
519 "<" + tools.ustr(from_account['email_id']) + ">",
520 'email_to':get_value(cursor,
526 'email_cc':get_value(cursor,
532 'email_bcc':get_value(cursor,
538 'subject':get_value(cursor,
541 template.def_subject,
544 'body_text':get_value(cursor,
547 template.def_body_text,
550 'body_html':get_value(cursor,
553 template.def_body_html,
556 'account_id' :from_account['id'],
557 #This is a mandatory field when automatic emails are sent
560 'mail_type':'multipart/alternative'
562 #Use signatures if allowed
563 if template.use_sign:
564 sign = self.pool.get('res.users').read(cursor,
568 context)['signature']
569 if mailbox_values['body_text']:
570 mailbox_values['body_text'] += sign
571 if mailbox_values['body_html']:
572 mailbox_values['body_html'] += sign
573 mailbox_id = self.pool.get('email_template.mailbox').create(
580 def generate_mail(self,
588 template = self.browse(cursor, user, template_id, context=context)
590 raise Exception("The requested template could not be loaded")
591 for record_id in record_ids:
592 mailbox_id = self._generate_mailbox_item_from_template(
598 mail = self.pool.get('email_template.mailbox').browse(
604 if template.report_template:
605 self._generate_attach_reports(
613 self.pool.get('email_template.mailbox').write(
624 class email_template_preview(osv.osv_memory):
625 _name = "email_template.preview"
626 _description = "Email Template Preview"
628 def _default_model(self, cursor, user, context=None):
630 Returns the default value for model field
631 @param cursor: Database Cursor
632 @param user: ID of current user
633 @param context: Open ERP Context
635 return self.pool.get('email.template').read(
638 context['active_id'],
640 context)['object_name']
643 'ref_template':fields.many2one(
645 'Template', readonly=True),
646 'rel_model':fields.many2one('ir.model', 'Model', readonly=True),
647 'to':fields.char('To', size=250, readonly=True),
648 'cc':fields.char('CC', size=250, readonly=True),
649 'bcc':fields.char('BCC', size=250, readonly=True),
650 'subject':fields.char('Subject', size=200, readonly=True),
651 'body_text':fields.text('Body', readonly=True),
652 'body_html':fields.text('Body', readonly=True),
653 'report':fields.char('Report Name', size=100, readonly=True),
656 'ref_template': lambda self, cr, uid, ctx:ctx['active_id'],
657 'rel_model': _default_model
660 def on_change_ref(self, cr, uid, ids, rel_model_ref, context=None):
663 if not rel_model_ref:
667 context = self.context
668 template = self.pool.get('email.template').browse(cr, uid, context['active_id'], context)
669 #Search translated template
670 lang = get_value(cr, uid, rel_model_ref, template.lang, template, context)
673 ctx.update({'lang':lang})
674 template = self.pool.get('email.template').browse(cr, uid, context['active_id'], ctx)
675 vals['to'] = get_value(cr, uid, rel_model_ref, template.def_to, template, context)
676 vals['cc'] = get_value(cr, uid, rel_model_ref, template.def_cc, template, context)
677 vals['bcc'] = get_value(cr, uid, rel_model_ref, template.def_bcc, template, context)
678 vals['subject'] = get_value(cr, uid, rel_model_ref, template.def_subject, template, context)
679 vals['body_text'] = get_value(cr, uid, rel_model_ref, template.def_body_text, template, context)
680 vals['body_html'] = get_value(cr, uid, rel_model_ref, template.def_body_html, template, context)
681 vals['report'] = get_value(cr, uid, rel_model_ref, template.file_name, template, context)
682 return {'value':vals}
684 email_template_preview()
686 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: