diff --git a/addons/sms/__manifest__.py b/addons/sms/__manifest__.py
index dad1ac221da..0de865810e7 100644
--- a/addons/sms/__manifest__.py
+++ b/addons/sms/__manifest__.py
@@ -18,6 +18,7 @@ The service is provided by the In App Purchase Odoo platform.
'views/res_partner_views.xml',
'views/assets.xml',
'views/sms_sms_views.xml',
+ 'views/sms_template_views.xml',
'security/ir.model.access.csv',
],
'qweb': [
diff --git a/addons/sms/models/__init__.py b/addons/sms/models/__init__.py
index b855b4b7955..4adcd67ab64 100644
--- a/addons/sms/models/__init__.py
+++ b/addons/sms/models/__init__.py
@@ -8,3 +8,4 @@ from . import mail_thread
from . import res_partner
from . import sms_api
from . import sms_sms
+from . import sms_template
diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py
index fd47be27a5d..b088309cbfe 100644
--- a/addons/sms/models/mail_thread.py
+++ b/addons/sms/models/mail_thread.py
@@ -84,6 +84,46 @@ class MailThread(models.AbstractModel):
result[record.id] = {'partner': self.env['res.partner'], 'sanitized': False, 'number': False}
return result
+ def _message_sms_schedule_mass(self, body='', template=False, active_domain=None):
+ """ Shortcut method to schedule a mass sms sending on a recordset.
+
+ :param template: an optional sms.template record;
+ :param active_domain: bypass self.ids and apply composer on active_domain
+ instead;
+ """
+ composer_context = {
+ 'default_res_model': self._name,
+ 'default_composition_mode': 'mass',
+ 'default_template_id': template.id if template else False,
+ 'default_body': body if body and not template else False,
+ }
+ if active_domain is not None:
+ composer_context['default_use_active_domain'] = True
+ composer_context['default_active_domain'] = repr(active_domain)
+ else:
+ composer_context['default_res_ids'] = self.ids
+
+ composer = self.env['sms.composer'].with_context(**composer_context).create({})
+ return composer._action_send_sms()
+
+ def _message_sms_with_template(self, template=False, template_xmlid=False, template_fallback='', partner_ids=False, **kwargs):
+ """ Shortcut method to perform a _message_sms with an sms.template.
+
+ :param template: a valid sms.template record;
+ :param template_xmlid: XML ID of an sms.template (if no template given);
+ :param template_fallback: plaintext (jinja-enabled) in case template
+ and template xml id are falsy (for example due to deleted data);
+ """
+ self.ensure_one()
+ if not template and template_xmlid:
+ template = self.env.ref(template_xmlid, raise_if_not_found=False)
+ if template:
+ template_w_lang = template._get_context_lang_per_id(self.ids)[self.id]
+ body = template._render_template(template_w_lang.body, self._name, self.ids)[self.id]
+ else:
+ body = self.env['sms.template']._render_template(template_fallback, self._name, self.ids)[self.id]
+ return self._message_sms(body, partner_ids=partner_ids, **kwargs)
+
def _message_sms(self, body, subtype_id=False, partner_ids=False, number_field=False,
sms_numbers=None, sms_pid_to_number=None, **kwargs):
""" Main method to post a message on a record using SMS-based notification
diff --git a/addons/sms/models/sms_template.py b/addons/sms/models/sms_template.py
new file mode 100644
index 00000000000..6835bac7eb6
--- /dev/null
+++ b/addons/sms/models/sms_template.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+
+
+class SMSTemplate(models.Model):
+ "Templates for sending SMS"
+ _name = "sms.template"
+ _description = 'SMS Templates'
+
+ @api.model
+ def default_get(self, fields):
+ res = super(SMSTemplate, self).default_get(fields)
+ if not fields or 'model_id' in fields and not res.get('model_id') and res.get('model'):
+ res['model_id'] = self.env['ir.model']._get(res['model']).id
+ return res
+
+ name = fields.Char()
+ model_id = fields.Many2one(
+ 'ir.model', string='Applies to', required=True,
+ domain=['&', ('is_mail_thread', '=', True), ('transient', '=', False)],
+ help="The type of document this template can be used with")
+ model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True)
+ body = fields.Char('Body', translate=True, required=True)
+ lang = fields.Char('Language', placeholder="${object.partner_id.lang}")
+ # Fake fields used to implement the placeholder assistant
+ model_object_field = fields.Many2one('ir.model.fields', string="Field", store=False,
+ help="Select target field from the related document model.\n"
+ "If it is a relationship field you will be able to select "
+ "a target field at the destination of the relationship.")
+ sub_object = fields.Many2one('ir.model', 'Sub-model', readonly=True, store=False,
+ help="When a relationship field is selected as first field, "
+ "this field shows the document model the relationship goes to.")
+ sub_model_object_field = fields.Many2one('ir.model.fields', 'Sub-field', store=False,
+ help="When a relationship field is selected as first field, "
+ "this field lets you select the target field within the "
+ "destination document model (sub-model).")
+ null_value = fields.Char('Default Value', store=False, help="Optional value to use if the target field is empty")
+ copyvalue = fields.Char('Placeholder Expression', store=False,
+ help="Final placeholder expression, to be copy-pasted in the desired template field.")
+
+ @api.onchange('model_object_field', 'sub_model_object_field', 'null_value')
+ def _onchange_dynamic_placeholder(self):
+ """ Generate the dynamic placeholder """
+ if self.model_object_field:
+ if self.model_object_field.ttype in ['many2one', 'one2many', 'many2many']:
+ model = self.env['ir.model']._get(self.model_object_field.relation)
+ if model:
+ self.sub_object = model.id
+ sub_field_name = self.sub_model_object_field.name
+ self.copyvalue = self._build_expression(self.model_object_field.name,
+ sub_field_name, self.null_value or False)
+ else:
+ self.sub_object = False
+ self.sub_model_object_field = False
+ self.copyvalue = self._build_expression(self.model_object_field.name, False, self.null_value or False)
+ else:
+ self.sub_object = False
+ self.copyvalue = False
+ self.sub_model_object_field = False
+ self.null_value = False
+
+ @api.model
+ def _build_expression(self, field_name, sub_field_name, null_value):
+ """Returns a placeholder expression for use in a template field,
+ based on the values provided in the placeholder assistant.
+
+ :param field_name: main field name
+ :param sub_field_name: sub field name (M2O)
+ :param null_value: default value if the target value is empty
+ :return: final placeholder expression """
+ expression = ''
+ if field_name:
+ expression = "${object." + field_name
+ if sub_field_name:
+ expression += "." + sub_field_name
+ if null_value:
+ expression += " or '''%s'''" % null_value
+ expression += "}"
+ return expression
+
+ @api.multi
+ @api.returns('self', lambda value: value.id)
+ def copy(self, default=None):
+ default = dict(default or {},
+ name=_("%s (copy)") % self.name)
+ return super(SMSTemplate, self).copy(default=default)
+
+ @api.multi
+ def _get_context_lang_per_id(self, res_ids):
+ self.ensure_one()
+ if res_ids is None:
+ return {None: self}
+
+ if self.env.context.get('template_preview_lang'):
+ lang = self.env.context.get('template_preview_lang')
+ results = dict((res_id, self.with_context(lang=lang)) for res_id in res_ids)
+ else:
+ rendered_langs = self._render_template(self.lang, self.model, res_ids)
+ results = dict(
+ (res_id, self.with_context(lang=lang) if lang else self)
+ for res_id, lang in rendered_langs.items())
+
+ return results
+
+ @api.multi
+ def _get_ids_per_lang(self, res_ids):
+ self.ensure_one()
+
+ rids_to_tpl = self._get_context_lang_per_id(res_ids)
+ tpl_to_rids = {}
+ for res_id, template in rids_to_tpl.items():
+ tpl_to_rids.setdefault(template._context.get('lang', self.env.user.lang), []).append(res_id)
+
+ return tpl_to_rids
+
+ @api.model
+ def _render_template(self, template_txt, model, res_ids):
+ """ Render the jinja template """
+ return self.env['mail.template']._render_template(template_txt, model, res_ids)
diff --git a/addons/sms/security/ir.model.access.csv b/addons/sms/security/ir.model.access.csv
index 42c4d624aaa..c906bc097b7 100644
--- a/addons/sms/security/ir.model.access.csv
+++ b/addons/sms/security/ir.model.access.csv
@@ -1,3 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sms_sms_all,access.sms.sms.all,model_sms_sms,,0,0,0,0
access_sms_sms_system,access.sms.sms.system,model_sms_sms,base.group_system,1,1,1,1
+access_sms_template_all,access.sms.template.all,model_sms_template,,0,0,0,0
+access_sms_template_user,access.sms.template.user,model_sms_template,base.group_user,1,0,0,0
+access_sms_template_system,access.sms.template.system,model_sms_template,base.group_system,1,1,1,0
diff --git a/addons/sms/tests/common.py b/addons/sms/tests/common.py
index 7c0c3b5d22a..f0f96c0e8d6 100644
--- a/addons/sms/tests/common.py
+++ b/addons/sms/tests/common.py
@@ -73,6 +73,8 @@ class MockSMS(common.BaseCase):
def assertSMSCanceled(self, partner, number, error_code, content=None):
""" Check canceled SMS. Search is done for a pair partner / number where
partner can be an empty recordset. """
+ if number is None and partner:
+ number = phone_validation.phone_get_sanitized_record_number(partner)
sms = self.env['sms.sms'].sudo().search([
('partner_id', '=', partner.id), ('number', '=', number),
('state', '=', 'canceled')
@@ -85,6 +87,8 @@ class MockSMS(common.BaseCase):
def assertSMSFailed(self, partner, number, error_code, content=None):
""" Check failed SMS. Search is done for a pair partner / number where
partner can be an empty recordset. """
+ if number is None and partner:
+ number = phone_validation.phone_get_sanitized_record_number(partner)
sms = self.env['sms.sms'].sudo().search([
('partner_id', '=', partner.id), ('number', '=', number),
('state', '=', 'error')
@@ -97,11 +101,13 @@ class MockSMS(common.BaseCase):
def assertSMSOutgoing(self, partner, number, content=None):
""" Check outgoing SMS. Search is done for a pair partner / number where
partner can be an empty recordset. """
+ if number is None and partner:
+ number = phone_validation.phone_get_sanitized_record_number(partner)
sms = self.env['sms.sms'].sudo().search([
('partner_id', '=', partner.id), ('number', '=', number),
('state', '=', 'outgoing')
])
- self.assertTrue(sms, 'SMS: not found failed SMS for %s (number: %s, state)' % (partner, number))
+ self.assertTrue(sms, 'SMS: not found failed SMS for %s (number: %s)' % (partner, number))
if content is not None:
self.assertEqual(sms.body, content)
diff --git a/addons/sms/views/res_partner_views.xml b/addons/sms/views/res_partner_views.xml
index 1a483568178..e841fad4005 100644
--- a/addons/sms/views/res_partner_views.xml
+++ b/addons/sms/views/res_partner_views.xml
@@ -15,12 +15,11 @@
+ context="{'default_partner_field_name': 'phone'}" />
@@ -31,14 +30,37 @@
type="action"
name="%(sms.sms_composer_action_form)d"
class="btn-sm btn-link mb4 fa fa-envelope-o"
- aria-label="Send SMS"
- title="Send SMS"
+ aria-label="Send SMS Text Message"
+ title="Send SMS Text Message"
attrs="{'invisible':[('mobile', '=', False)]}"
- context="{'field_name': 'mobile'}"
- />
-
-
+ context="{'default_partner_field_name': 'mobile'}" />
+
+
+
+
+
+
diff --git a/addons/sms/views/sms_template_views.xml b/addons/sms/views/sms_template_views.xml
new file mode 100644
index 00000000000..07d752f31b6
--- /dev/null
+++ b/addons/sms/views/sms_template_views.xml
@@ -0,0 +1,71 @@
+
+
+
+ sms.template.view.form
+ sms.template
+
+
+
+
+
+
+ sms.template.view.tree
+ sms.template
+
+
+
+
+
+
+
+
+
+ sms.template.view.search
+ sms.template
+
+
+
+
+
+
+
+
+
+ Templates
+ sms.template
+ tree,form
+
+
+
+
+
+
diff --git a/addons/sms/wizard/sms_composer.py b/addons/sms/wizard/sms_composer.py
index 93b3202a83a..c4ab81f0ede 100644
--- a/addons/sms/wizard/sms_composer.py
+++ b/addons/sms/wizard/sms_composer.py
@@ -1,58 +1,261 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from ast import literal_eval
+
from odoo import api, fields, models, _
from odoo.addons.phone_validation.tools import phone_validation
from odoo.exceptions import UserError
+from odoo.tools.safe_eval import safe_eval
class SendSMS(models.TransientModel):
_name = 'sms.composer'
- _description = 'Send SMS'
-
- recipients = fields.Char('Recipients', required=True)
- message = fields.Text('Message', required=True)
-
- def _get_records(self, model):
- if self.env.context.get('active_domain'):
- records = model.search(self.env.context.get('active_domain'))
- elif self.env.context.get('active_ids'):
- records = model.browse(self.env.context.get('active_ids', []))
- else:
- records = model.browse(self.env.context.get('active_id', []))
- return records
+ _description = 'Send SMS Wizard'
+ _RECIPIENTS_DISPLAY_NBR = 3
@api.model
def default_get(self, fields):
result = super(SendSMS, self).default_get(fields)
- active_model = self.env.context.get('active_model')
+ if fields == 'partner_ids':
+ # shortcut because default_get in cache, avoid issues
+ return result
- if not self.env.context.get('default_recipients') and active_model and hasattr(self.env[active_model], '_sms_get_default_partners'):
- model = self.env[active_model]
- records = self._get_records(model)
- partners = records._sms_get_default_partners()
- phone_numbers = []
- no_phone_partners = []
- for partner in partners:
- number = phone_validation.phone_get_sanitized_record_number(partner, self.env.context.get('field_name') or 'mobile', 'country_id')
- if number:
- phone_numbers.append(number)
- else:
- no_phone_partners.append(partner.name)
- if len(partners) > 1:
- if no_phone_partners:
- raise UserError(_('Missing mobile number for %s.') % ', '.join(no_phone_partners))
- result['recipients'] = ', '.join(phone_numbers)
+ result['res_model'] = result.get('res_model') or self.env.context.get('active_model')
+ result['composition_mode'] = result.get('composition_mode') or 'comment'
+
+ if result['composition_mode'] == 'comment' and not result.get('res_id'):
+ result['res_id'] = self.env.context.get('active_id')
+ if result['composition_mode'] != 'comment':
+ if result.get('use_active_domain') and not result.get('active_domain'):
+ result['active_domain'] = self.env.context['active_domain']
+ elif not result.get('res_ids'):
+ result['res_ids'] = repr(self.env.context.get('active_ids'))
+
+ if result['res_model']:
+ result.update(
+ self._get_composer_values(
+ result['composition_mode'], result['res_model'], result.get('res_id'),
+ result.get('body'), result.get('template_id')
+ )
+ )
return result
- def action_send_sms(self):
- numbers = [number.strip() for number in self.recipients.split(',') if number.strip()]
+ # documents
+ composition_mode = fields.Selection([
+ ('numbers', 'Send to numbers'),
+ ('comment', 'Post on a document'),
+ ('mass', 'Send SMS in batch')],
+ string='Composition Mode', default='comment', required=True)
+ res_model = fields.Char('Document Model Name')
+ res_id = fields.Integer('Document ID')
+ res_ids = fields.Char('Document IDs')
+ use_active_domain = fields.Boolean('Use active domain')
+ active_domain = fields.Text('Active domain', readonly=True)
+ # options for comment and mass mode
+ mass_keep_log = fields.Boolean('Keep a note on document')
+ # recipients
+ recipient_description = fields.Text('Recipients (Partners)', compute='_compute_description')
+ recipient_invalid = fields.Text('Invalid recipients', compute='_compute_description')
+ number_field_name = fields.Char(string='Field holding number')
+ partner_ids = fields.Many2many('res.partner')
+ numbers = fields.Char('Recipients (Numbers)')
+ sanitized_numbers = fields.Char('Sanitized Number', compute='_compute_sanitized_numbers')
+ # content
+ template_id = fields.Many2one('sms.template', string='Use Template', domain="[('model', '=', res_model)]")
+ body = fields.Text('Message', required=True)
+
+ @api.depends('partner_ids', 'res_model', 'res_id', 'res_ids', 'use_active_domain', 'composition_mode', 'number_field_name', 'sanitized_numbers')
+ def _compute_description(self):
+ if self.partner_ids:
+ description = ''
+ description = ','.join('%s - %s' % (partner.display_name, partner.mobile or partner.phone) for partner in self.partner_ids[:self._RECIPIENTS_DISPLAY_NBR])
+ if len(self.partner_ids) > self._RECIPIENTS_DISPLAY_NBR:
+ description += _(' (and %s more)') % (len(self.partner_ids) - self._RECIPIENTS_DISPLAY_NBR)
+ self.recipient_description = description
+ self.recipient_invalid = False
+
+ elif self.composition_mode in ('comment', 'mass') and self.res_model:
+ records = None
+ if self.composition_mode == 'comment' and self.res_id:
+ records = self.env[self.res_model].browse(self.res_id)
+ elif self.composition_mode == 'mass' and self.res_ids:
+ records = self.env[self.res_model].browse(literal_eval(self.res_ids))
+
+ if records and issubclass(type(records), self.pool['mail.thread']):
+ res = records._sms_get_recipients_info(force_field=self.number_field_name)
+ valid_ids = [rid for rid, rvalues in res.items() if rvalues['sanitized']]
+ invalid_ids = [rid for rid, rvalues in res.items() if not rvalues['sanitized']]
+
+ self.recipient_description = ', '.join('%s (%s)' % (
+ res[record.id]['partner'].name or record.display_name,
+ res[record.id]['sanitized'])
+ for record in records if record.id in valid_ids[:self._RECIPIENTS_DISPLAY_NBR]
+ ) or False
+ if len(valid_ids) > self._RECIPIENTS_DISPLAY_NBR:
+ self.recipient_description += _(', and %s more') % (len(valid_ids) - self._RECIPIENTS_DISPLAY_NBR)
+
+ self.recipient_invalid = ', '.join('%s (%s)' % (
+ res[record.id]['partner'].display_name or record.display_name,
+ res[record.id]['number'])
+ for record in records if record.id in invalid_ids[:self._RECIPIENTS_DISPLAY_NBR]
+ ) or False
+ if len(invalid_ids) > self._RECIPIENTS_DISPLAY_NBR:
+ self.recipient_description += _(', and %s more') % (len(invalid_ids) - self._RECIPIENTS_DISPLAY_NBR)
+ else:
+ self.recipient_description = ''
+ self.recipient_invalid = '' if self.sanitized_numbers or (self.composition_mode == 'mass' and self.use_active_domain) else _('No record found')
- active_model = self.env.context.get('active_model')
- if active_model and hasattr(self.env[active_model], '_message_sms'):
- model = self.env[active_model]
- records = self._get_records(model)
- records[0]._message_sms(self.message, sms_numbers=numbers)
else:
- self.env['sms.api']._send_sms(numbers, self.message)
+ self.recipient_description = False
+
+ @api.depends('numbers', 'res_model', 'res_id')
+ def _compute_sanitized_numbers(self):
+ if self.numbers:
+ record = self._get_records() if self.res_model and self.res_id else self.env.user
+ sanitize_res = phone_validation.phone_sanitize_numbers_string_w_record(self.numbers, record)
+ sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']]
+ invalid_numbers = [number for number, info in sanitize_res.items() if info['code']]
+ if invalid_numbers:
+ raise UserError(_('Following numbers are not correctly encoded: %s') % repr(invalid_numbers))
+ self.sanitized_numbers = ','.join(sanitized_numbers)
+ else:
+ self.sanitized_numbers = False
+
+ @api.onchange('composition_mode', 'res_model', 'res_id', 'template_id')
+ def _onchange_template_id(self):
+ if self.template_id and self.composition_mode == 'comment':
+ self.body = self.template_id._render_template(self.template_id.body, self.res_model, [self.res_id])[self.res_id]
+ elif self.template_id:
+ self.body = self.template_id.body
+
+ # ------------------------------------------------------------
+ # Actions
+ # ------------------------------------------------------------
+
+ def action_send_sms(self):
+ if self.recipient_invalid:
+ raise UserError(_('Invalid recipients: %s') % self.recipient_invalid)
+ self._action_send_sms()
+ if self.composition_mode == 'comment':
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'reload',
+ }
+ return False
+
+ def _action_send_sms(self, force_send=False):
+ records = self._get_records()
+ if self.composition_mode == 'numbers':
+ return self._action_send_sms_numbers()
+ elif self.composition_mode == 'comment':
+ if records is not None and issubclass(type(records), self.pool['mail.thread']):
+ return self._action_send_sms_comment(records)
+ return self._action_send_sms_numbers()
+ elif self.mass_keep_log:
+ return self._action_send_sms_mass_w_log(records)
+ else:
+ return self._action_send_sms_mass(records)
+
+ def _action_send_sms_numbers(self):
+ self.env['sms.api']._send_sms_batch([{
+ 'res_id': 0,
+ 'number': number,
+ 'content': self.body,
+ } for number in self.sanitized_numbers.split(',')])
return True
+
+ def _action_send_sms_comment(self, records=None):
+ records = records if records is not None else self._get_records()
+ subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment')
+
+ messages = self.env['mail.message']
+ for record in records:
+ messages |= records._message_sms(
+ self.body, subtype_id=subtype_id,
+ partner_ids=self.partner_ids.ids or False,
+ number_field=self.number_field_name,
+ sms_numbers=self.sanitized_numbers.split(',') if self.sanitized_numbers else None)
+ return messages
+
+ def _action_send_sms_mass(self, records=None):
+ records = records if records is not None else self._get_records()
+
+ record_values = self._prepare_mass_sms_values(records)
+ sms_create_vals = [record_values[record.id] for record in records]
+ sms = self.env['sms.sms'].sudo().create(sms_create_vals)
+ return sms
+
+ def _action_send_sms_mass_w_log(self):
+ records = self._get_records()
+ if records and hasattr(records, '_message_sms'):
+ subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
+ for record in records:
+ record._message_sms(self.body, subtype_id=subtype_id, partner_ids=False, sms_numbers=None)
+ else:
+ self.env['sms.api']._send_sms(self.numbers, self.body)
+ return True
+
+ # ------------------------------------------------------------
+ # Mass mode specific
+ # ------------------------------------------------------------
+
+ def _prepare_recipient_values(self, records=None):
+ records = records if records is not None else self._get_records()
+ recipients_info = records._sms_get_recipients_info(force_field=self.number_field_name)
+ return recipients_info
+
+ def _prepare_body_values(self, records=None):
+ records = records if records is not None else self._get_records()
+ if self.template_id and self.body == self.template_id.body:
+ lang_to_rids = self.template_id._get_ids_per_lang(records.ids)
+ all_bodies = {}
+ for lang, rids in lang_to_rids.items():
+ template = self.template_id.with_context(lang=lang)
+ all_bodies.update(template._render_template(template.body, records._name, rids))
+ else:
+ all_bodies = self.env['mail.template']._render_template(self.body, records._name, records.ids)
+ return all_bodies
+
+ def _prepare_mass_sms_values(self, records=None):
+ records = records if records is not None else self._get_records()
+ all_bodies = self._prepare_body_values(records=records)
+ all_recipients = self._prepare_recipient_values(records=records)
+
+ result = {}
+ for record in records:
+ recipients = all_recipients[record.id]
+ result[record.id] = {
+ 'body': all_bodies[record.id],
+ 'partner_id': recipients['partner'].id,
+ 'number': recipients['sanitized'] or recipients['number'],
+ 'state': 'outgoing' if recipients['sanitized'] else 'error',
+ }
+ return result
+
+ # ------------------------------------------------------------
+ # Tools
+ # ------------------------------------------------------------
+
+ def _get_composer_values(self, composition_mode, res_model, res_id, body, template_id):
+ result = {}
+ if composition_mode == 'comment':
+ if not body and template_id:
+ template = self.env['sms.template'].browse(template_id)
+ result['body'] = template._render_template(template.body, res_model, [res_id])[res_id]
+ else:
+ if not body and template_id:
+ template = self.env['sms.template'].browse(template_id)
+ result['body'] = template.body
+ return result
+
+ def _get_records(self):
+ if not self.res_model:
+ return None
+ if self.use_active_domain: # TDE FIXME: clear active_domain (False,[], ..)
+ records = self.env[self.res_model].search(safe_eval(self.active_domain))
+ elif self.res_id:
+ records = self.env[self.res_model].browse(self.res_id)
+ else:
+ records = self.env[self.res_model].browse(literal_eval(self.res_ids))
+ return records
diff --git a/addons/sms/wizard/sms_composer_views.xml b/addons/sms/wizard/sms_composer_views.xml
index 4b6651a00cb..0ed78dbccda 100644
--- a/addons/sms/wizard/sms_composer_views.xml
+++ b/addons/sms/wizard/sms_composer_views.xml
@@ -7,35 +7,76 @@
+
- Send SMS
+ Send SMS Text Message
sms.composer
form
new
-
-
-
diff --git a/addons/test_mail_full/tests/__init__.py b/addons/test_mail_full/tests/__init__.py
index b5c867844ba..733d20548ce 100644
--- a/addons/test_mail_full/tests/__init__.py
+++ b/addons/test_mail_full/tests/__init__.py
@@ -5,3 +5,4 @@ from . import test_sms_composer
from . import test_sms_performance
from . import test_sms_post
from . import test_sms_sms
+from . import test_sms_template
diff --git a/addons/test_mail_full/tests/test_sms_composer.py b/addons/test_mail_full/tests/test_sms_composer.py
index dd2aaf64ae7..8ea2cc2c197 100644
--- a/addons/test_mail_full/tests/test_sms_composer.py
+++ b/addons/test_mail_full/tests/test_sms_composer.py
@@ -7,66 +7,303 @@ from odoo.addons.test_mail.tests import common as test_mail_common
from odoo.addons.test_mail_full.tests import common as test_mail_full_common
-class TestSMSComposer(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.MockEmails, test_mail_common.TestRecipients):
+class TestSMSComposerComment(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.MockEmails, test_mail_common.TestRecipients):
+ """ TODO LIST
+
+ * add test for default_res_model / default_res_id and stuff like that;
+ * add test for comment put in queue;
+ * add test for language support (set template lang context);
+ * add test for sanitized / wrong numbers;
+ """
@classmethod
def setUpClass(cls):
- super(TestSMSComposer, cls).setUpClass()
+ super(TestSMSComposerComment, cls).setUpClass()
cls._test_body = 'VOID CONTENT'
+
cls.partner_numbers = [
phone_validation.phone_format(partner.mobile, partner.country_id.code, partner.country_id.phone_code, force_format='E164')
for partner in (cls.partner_1 | cls.partner_2)
]
- def test_composer_no_model(self):
- composer = self.env['sms.composer'].with_context().create({
- 'message': self._test_body,
- 'recipients': '+32475998877, 0475997788'
+ cls.test_record = cls.env['mail.test.sms'].with_context(**cls._test_context).create({
+ 'name': 'Test',
+ 'customer_id': cls.partner_1.id,
+ 'mobile_nbr': cls.test_numbers[0],
+ 'phone_nbr': cls.test_numbers[1],
+ })
+ cls.test_record = cls._reset_mail_context(cls.test_record)
+
+ cls.sms_template = cls.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': cls.env['ir.model']._get('mail.test.sms').id,
+ 'body': 'Dear ${object.display_name} this is an SMS.',
})
- with self.mockSMSGateway():
- composer.action_send_sms()
- # self.assertSMSSent(self.random_numbers, self._test_body) # TDE FIXME: actually sanitizer is not called on numbers in current master (saas 12.3)
- self.assertSMSSent('+32475998877, 0475997788'.split(', '), self._test_body)
-
- def test_composer_no_mail_thread(self):
- composer = self.env['sms.composer'].with_context(active_model='test_performance.base').create({
- 'message': self._test_body,
- 'recipients': '+32475998877, 0475997788'
- })
-
- with self.mockSMSGateway():
- composer.action_send_sms()
- # self.assertSMSSent(self.random_numbers, self._test_body) # TDE FIXME: actually sanitizer is not called on numbers in current master (saas 12.3)
- self.assertSMSSent('+32475998877, 0475997788'.split(', '), self._test_body)
-
- def test_composer_partners_active_domain(self):
- partners = self.partner_1 | self.partner_2
- composer = self.env['sms.composer'].with_context(
- active_model='res.partner',
- active_domain=[('id', 'in', partners.ids)]
- ).create({
- 'message': self._test_body,
- })
-
- with self.mockSMSGateway():
- composer.action_send_sms()
- self.assertSMSSent(self.partner_numbers, self._test_body)
-
- def test_composer_partners_sanitize(self):
- partner_incorrect = self.env['res.partner'].create({
- 'name': 'Jean-Claude Incorrect',
- 'email': 'jean.claude@example.com',
- 'mobile': 'coincoin',
+ def test_composer_comment_not_mail_thread(self):
+ with self.sudo('employee'):
+ record = self.env['test_performance.base'].create({'name': 'TestBase'})
+ composer = self.env['sms.composer'].with_context(
+ active_model='test_performance.base', active_id=record.id
+ ).create({
+ 'body': self._test_body,
+ 'numbers': ','.join(self.random_numbers),
})
- partners = self.partner_1 | self.partner_2 | partner_incorrect
- # composer = self.env['sms.composer'].with_context(
- # active_model='res.partner',
- # active_domain=[('id', 'in', partners.ids)]
- # ).create({
- # 'message': self._test_body,
- # })
- # with self.mockSMSGateway():
- # composer.action_send_sms()
- # self.assertSMSSent((self.partner_1 | self.partner_2).mapped('mobile'), test_body) # TDE FIXME: actually sanitizer does not work in current master (saas 12.23))
\ No newline at end of file
+ with self.mockSMSGateway():
+ composer._action_send_sms()
+
+ self.assertSMSSent(self.random_numbers_san, self._test_body)
+
+ def test_composer_comment_default(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ active_model='mail.test.sms', active_id=self.test_record.id
+ ).create({
+ 'body': self._test_body,
+ })
+
+ with self.mockSMSGateway():
+ messages = composer._action_send_sms()
+
+ self.assertSMSNotification([{'partner': self.test_record.customer_id, 'number': self.test_numbers_san[1]}], self._test_body, messages)
+
+ def test_composer_comment_field_1(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ active_model='mail.test.sms', active_id=self.test_record.id,
+ ).create({
+ 'body': self._test_body,
+ 'number_field_name': 'mobile_nbr',
+ })
+
+ with self.mockSMSGateway():
+ messages = composer._action_send_sms()
+
+ self.assertSMSNotification([{'partner': self.test_record.customer_id, 'number': self.test_numbers_san[0]}], self._test_body, messages)
+
+ def test_composer_comment_field_2(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ active_model='mail.test.sms', active_id=self.test_record.id,
+ ).create({
+ 'body': self._test_body,
+ 'number_field_name': 'phone_nbr',
+ })
+
+ with self.mockSMSGateway():
+ messages = composer._action_send_sms()
+
+ self.assertSMSNotification([{'partner': self.test_record.customer_id, 'number': self.test_numbers_san[1]}], self._test_body, messages)
+
+ def test_composer_comment_field_w_numbers(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ active_model='mail.test.sms', active_id=self.test_record.id,
+ default_number_field_name='mobile_nbr',
+ ).create({
+ 'body': self._test_body,
+ 'numbers': ','.join(self.random_numbers),
+ })
+
+ with self.mockSMSGateway():
+ messages = composer._action_send_sms()
+
+ self.assertSMSNotification([
+ {'partner': self.test_record.customer_id, 'number': self.test_record.mobile_nbr},
+ {'number': self.random_numbers_san[0]}, {'number': self.random_numbers_san[1]}], self._test_body, messages)
+
+ def test_composer_comment_field_w_template(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ active_model='mail.test.sms', active_id=self.test_record.id,
+ default_template_id=self.sms_template.id,
+ default_number_field_name='mobile_nbr',
+ ).create({})
+
+ with self.mockSMSGateway():
+ messages = composer._action_send_sms()
+
+ self.assertSMSNotification([{'partner': self.test_record.customer_id, 'number': self.test_record.mobile_nbr}], 'Dear %s this is an SMS.' % self.test_record.display_name, messages)
+
+ def test_composer_numbers_no_model(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='numbers'
+ ).create({
+ 'body': self._test_body,
+ 'numbers': ','.join(self.random_numbers),
+ })
+
+ with self.mockSMSGateway():
+ composer._action_send_sms()
+ self.assertSMSSent(self.random_numbers_san, self._test_body)
+
+ # def test_composer_partners_sanitize(self):
+ # partner_incorrect = self.env['res.partner'].create({
+ # 'name': 'Jean-Claude Incorrect',
+ # 'email': 'jean.claude@example.com',
+ # 'mobile': 'coincoin',
+ # })
+ # partners = self.partner_1 | self.partner_2 | partner_incorrect
+ # with self.sudo('employee'):
+ # composer = self.env['sms.composer'].with_context(
+ # active_model='res.partner',
+ # active_domain=[('id', 'in', partners.ids)]
+ # ).create({
+ # 'body': self._test_body,
+ # })
+
+ # with self.mockSMSGateway():
+ # composer.action_send_sms()
+ # self.assertSMSSent((self.partner_1 | self.partner_2).mapped('mobile'), test_body)
+
+
+class TestSMSComposerMass(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.MockEmails, test_mail_common.TestRecipients):
+ """ TODO LIST
+
+ * add test for mass with log note
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestSMSComposerMass, cls).setUpClass()
+ cls._test_body = 'Zizisse an SMS.'
+
+ records = cls.env['mail.test.sms']
+ partners = cls.env['res.partner']
+ country_id = cls.env.ref('base.be').id,
+ for x in range(3):
+ partners += cls.env['res.partner'].with_context(**cls._test_context).create({
+ 'name': 'Partner_%s' % (x),
+ 'email': '_test_partner_%s@example.com' % (x),
+ 'country_id': country_id,
+ 'mobile': '047500%s%s99' % (x, x)
+ })
+ records += cls.env['mail.test.sms'].with_context(**cls._test_context).create({
+ 'name': 'Test_%s' % (x),
+ 'customer_id': partners[x].id,
+ })
+ cls.records = cls._reset_mail_context(records)
+ cls.partners = partners
+
+ cls.sms_template = cls.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': cls.env['ir.model']._get('mail.test.sms').id,
+ 'body': 'Dear ${object.display_name} this is an SMS.',
+ })
+
+ def test_composer_mass_active_domain(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ default_use_active_domain=True,
+ active_domain=repr([('id', 'in', self.records.ids)]),
+ ).create({
+ 'body': self._test_body,
+ })
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for record in self.records:
+ self.assertSMSOutgoing(record.customer_id, None, self._test_body)
+
+ def test_composer_mass_active_domain_w_template(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ default_use_active_domain=True,
+ active_domain=repr([('id', 'in', self.records.ids)]),
+ default_template_id=self.sms_template.id,
+ ).create({
+ })
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for record in self.records:
+ self.assertSMSOutgoing(record.customer_id, None, 'Dear %s this is an SMS.' % record.display_name)
+
+ def test_composer_mass_active_ids(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ active_ids=self.records.ids,
+ ).create({
+ 'body': self._test_body,
+ })
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for partner in self.partners:
+ self.assertSMSOutgoing(partner, None, self._test_body)
+
+ def test_composer_mass_active_ids_w_template(self):
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ active_ids=self.records.ids,
+ default_template_id=self.sms_template.id,
+ ).create({})
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for record in self.records:
+ self.assertSMSOutgoing(record.customer_id, None, 'Dear %s this is an SMS.' % record.display_name)
+
+ def test_composer_mass_active_ids_w_template_and_lang(self):
+ self.env.ref('base.lang_fr').write({'active': True})
+ self.env['ir.translation'].create({
+ 'type': 'model',
+ 'name': 'sms.template,body',
+ 'lang': 'fr_FR',
+ 'res_id': self.sms_template.id,
+ 'src': self.sms_template.body,
+ 'value': 'Cher·e· ${object.display_name} ceci est un SMS.',
+ })
+ # set template to try to use customer lang
+ self.sms_template.write({
+ 'lang': '${object.customer_id.lang}',
+ })
+ # set one customer as french speaking
+ self.partners[2].write({'lang': 'fr_FR'})
+
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ active_ids=self.records.ids,
+ default_template_id=self.sms_template.id,
+ ).create({})
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for record in self.records:
+ if record.customer_id == self.partners[2]:
+ self.assertSMSOutgoing(record.customer_id, None, 'Cher·e· %s ceci est un SMS.' % record.display_name)
+ else:
+ self.assertSMSOutgoing(record.customer_id, None, 'Dear %s this is an SMS.' % record.display_name)
+
+ def test_message_schedule_sms(self):
+ with self.sudo('employee'):
+ with self.mockSMSGateway():
+ self.env['mail.test.sms'].browse(self.records.ids)._message_sms_schedule_mass(body=self._test_body)
+
+ for record in self.records:
+ self.assertSMSOutgoing(record.customer_id, None, self._test_body)
+
+ def test_message_schedule_sms_w_template(self):
+ with self.sudo('employee'):
+ with self.mockSMSGateway():
+ self.env['mail.test.sms'].browse(self.records.ids)._message_sms_schedule_mass(template=self.sms_template)
+
+ for record in self.records:
+ self.assertSMSOutgoing(record.customer_id, None, 'Dear %s this is an SMS.' % record.display_name)
diff --git a/addons/test_mail_full/tests/test_sms_performance.py b/addons/test_mail_full/tests/test_sms_performance.py
index 65c9e2cb4b5..a33464069f4 100644
--- a/addons/test_mail_full/tests/test_sms_performance.py
+++ b/addons/test_mail_full/tests/test_sms_performance.py
@@ -15,6 +15,7 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.MockSMS):
super(TestSMSPerformance, self).setUp()
self.user_employee.write({
'login': 'employee',
+ 'country_id': self.env.ref('base.be').id,
})
self.admin = self.env.user
@@ -86,3 +87,63 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.MockSMS):
self.assertEqual(record.message_ids[0].body, 'Performance Test
')
self.assertSMSNotification([{'partner': self.customer}], 'Performance Test', messages)
+
+
+@tagged('mail_performance')
+class TestSMSMassPerformance(BaseMailPerformance, sms_common.MockSMS):
+
+ def setUp(self):
+ super(TestSMSMassPerformance, self).setUp()
+ be_country_id = self.env.ref('base.be').id,
+ self.user_employee.write({
+ 'login': 'employee',
+ 'country_id': be_country_id,
+ })
+ self.admin = self.env.user
+ self.admin.write({
+ 'country_id': be_country_id,
+ })
+
+ self._test_body = 'MASS SMS'
+
+ records = self.env['mail.test.sms']
+ partners = self.env['res.partner']
+ for x in range(50):
+ partners += self.env['res.partner'].with_context(**self._quick_create_ctx).create({
+ 'name': 'Partner_%s' % (x),
+ 'email': '_test_partner_%s@example.com' % (x),
+ 'country_id': be_country_id,
+ 'mobile': '047500%s%s99' % (x, x)
+ })
+ records += self.env['mail.test.sms'].with_context(**self._quick_create_ctx).create({
+ 'name': 'Test_%s' % (x),
+ 'customer_id': partners[x].id,
+ })
+ self.partners = partners
+ self.records = records
+
+ self.sms_template = self.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': self.env['ir.model']._get('mail.test.sms').id,
+ 'body': 'Dear ${object.display_name} this is an SMS.',
+ })
+
+ @mute_logger('odoo.addons.sms.models.sms_sms')
+ @users('employee')
+ @warmup
+ def test_composer_mass_active_domain(self):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ default_use_active_domain=True,
+ active_domain=repr([('id', 'in', self.records.ids)]),
+ ).create({
+ 'body': self._test_body,
+ })
+
+ with self.mockSMSGateway(), self.assertQueryCount(employee=108):
+ composer.action_send_sms()
+
+ # TDE FIXME
+ # for record in self.records:
+ # self.assertSMSOutgoing(record.customer_id, None, self._test_body)
diff --git a/addons/test_mail_full/tests/test_sms_post.py b/addons/test_mail_full/tests/test_sms_post.py
index f1e7d91b1d0..686ed941415 100644
--- a/addons/test_mail_full/tests/test_sms_post.py
+++ b/addons/test_mail_full/tests/test_sms_post.py
@@ -170,6 +170,48 @@ class TestSMSPost(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS,
self.assertSMSNotification([{'partner': self.partner_1}, {'number': self.random_numbers_san[0]}], self._test_body, messages)
+ def test_message_sms_with_template(self):
+ sms_template = self.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': self.env['ir.model']._get('mail.test.sms').id,
+ 'body': 'Dear ${object.display_name} this is an SMS.',
+ })
+
+ with self.sudo('employee'):
+ with self.mockSMSGateway():
+ test_record = self.env['mail.test.sms'].browse(self.test_record.id)
+ messages = test_record._message_sms_with_template(template=sms_template)
+
+ self.assertSMSNotification([{'partner': self.partner_1, 'number': self.test_numbers_san[1]}], 'Dear %s this is an SMS.' % self.test_record.display_name, messages)
+
+ def test_message_sms_with_template_fallback(self):
+ with self.sudo('employee'):
+ with self.mockSMSGateway():
+ test_record = self.env['mail.test.sms'].browse(self.test_record.id)
+ messages = test_record._message_sms_with_template(template_xmlid='test_mail_full.this_should_not_exists', template_fallback='Fallback for ${object.id}')
+
+ self.assertSMSNotification([{'partner': self.partner_1, 'number': self.test_numbers_san[1]}], 'Fallback for %s' % self.test_record.id, messages)
+
+ def test_message_sms_with_template_xmlid(self):
+ sms_template = self.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': self.env['ir.model']._get('mail.test.sms').id,
+ 'body': 'Dear ${object.display_name} this is an SMS.',
+ })
+ self.env['ir.model.data'].create({
+ 'name': 'this_should_exists',
+ 'module': 'test_mail_full',
+ 'model': sms_template._name,
+ 'res_id': sms_template.id,
+ })
+
+ with self.sudo('employee'):
+ with self.mockSMSGateway():
+ test_record = self.env['mail.test.sms'].browse(self.test_record.id)
+ messages = test_record._message_sms_with_template(template_xmlid='test_mail_full.this_should_exists')
+
+ self.assertSMSNotification([{'partner': self.partner_1, 'number': self.test_numbers_san[1]}], 'Dear %s this is an SMS.' % self.test_record.display_name, messages)
+
class TestSMSPostException(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.MockEmails, test_mail_common.TestRecipients):
diff --git a/addons/test_mail_full/tests/test_sms_template.py b/addons/test_mail_full/tests/test_sms_template.py
new file mode 100644
index 00000000000..dab46cd04c7
--- /dev/null
+++ b/addons/test_mail_full/tests/test_sms_template.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.sms.tests import common as sms_common
+from odoo.addons.test_mail.tests import common as test_mail_common
+from odoo.addons.test_mail_full.tests import common as test_mail_full_common
+
+
+class TestSmsTemplate(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.MockEmails, test_mail_common.TestRecipients):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestSmsTemplate, cls).setUpClass()
+ cls.test_record = cls.env['mail.test.sms'].with_context(**cls._test_context).create({
+ 'name': 'Test',
+ 'customer_id': cls.partner_1.id,
+ })
+ cls.test_record = cls._reset_mail_context(cls.test_record)
+
+ cls.body_en = 'Dear ${object.display_name} this is an SMS.'
+ cls.body_fr = u"Hello ${object.display_name} ceci est en français."
+ cls.sms_template = cls.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': cls.env['ir.model']._get('mail.test.sms').id,
+ 'body': cls.body_en,
+ })
+
+ def test_sms_template_render(self):
+ rendered_body = self.sms_template._render_template(self.sms_template.body, self.sms_template.model, self.test_record.ids)
+ self.assertEqual(rendered_body[self.test_record.id], 'Dear %s this is an SMS.' % self.test_record.display_name)
+
+ def test_sms_template_lang(self):
+ self.env.ref('base.lang_fr').write({'active': True})
+ self.user_admin.write({'lang': 'en_US'})
+ self.env['ir.translation'].create({
+ 'type': 'model',
+ 'name': 'sms.template,body',
+ 'lang': 'fr_FR',
+ 'res_id': self.sms_template.id,
+ 'src': self.sms_template.body,
+ 'value': self.body_fr,
+ })
+ # set template to try to use customer lang
+ self.sms_template.write({
+ 'lang': '${object.customer_id.lang}',
+ })
+ # create a second record linked to a customer in another language
+ self.partner_2.write({
+ 'lang': 'fr_FR',
+ })
+ test_record_2 = self.env['mail.test.sms'].create({
+ 'name': 'Test',
+ 'customer_id': self.partner_2.id,
+ })
+
+ self.assertEqual(self.sms_template.body, self.body_en)
+ self.assertEqual(self.sms_template.with_context(lang='fr_FR').body, self.body_fr)
+
+ rid_to_tpl = self.sms_template._get_context_lang_per_id((self.test_record | test_record_2).ids)
+ self.assertEqual(set(rid_to_tpl.keys()), set((self.test_record | test_record_2).ids))
+ for rid, tpl in rid_to_tpl.items():
+ if rid == self.test_record.id:
+ self.assertEqual(tpl._context.get('lang'), 'en_US')
+ elif rid == test_record_2.id:
+ self.assertEqual(tpl._context.get('lang'), 'fr_FR')
+ else:
+ self.assertTrue(False)
+
+ lang_to_rids = self.sms_template._get_ids_per_lang((self.test_record | test_record_2).ids)
+ for lang, rids in lang_to_rids.items():
+ if lang == 'en_US':
+ self.assertEqual(rids, self.test_record.ids)
+ elif lang == 'fr_FR':
+ self.assertEqual(rids, test_record_2.ids)
+ else:
+ self.assertTrue(False, 'Should not return lang %s' % lang)