diff options
| author | Thibault Delavallée <tde@odoo.com> | 2018-12-04 16:00:12 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2019-01-14 15:22:18 +0000 |
| commit | 6fa46886931b29e4e7130c04d133657f3dffd6e3 (patch) | |
| tree | 610b1143d159697b5a008c539e636a5919af6a62 | |
| parent | 1c5371a966632eef2b6eaf67b755d13f529e7988 (diff) | |
[REF] survey: refactor invitation wizard
Purpose is to clean and simplify invite wizard on surveys. Main purpose is
to simplify options.
Public link is displayed for public surveys, allowing to quickly share
it. When sending emails a token is now generated for each recipient. There is
no "batch public link" sending option anymore as it is not really necessary.
Check is done on recipients and emails to match the access mode option. Having
a restricted survey gives the same limitations on people to invite (users,
employees, ...).
Code is cleaned to ease its understanding and prepare future evolution notably
with appraisals.
An existing mode field is added on invite model. All options are currently
not used but it prepares future addition to survey, notably for certifications.
Its purpose is to know what to do with existing tokens when inviting new
recipients. It is used notably to resend tokens through the user_input form
view instead of relying on a custom context key.
This commit is linked to task ID 1911586 and PR #28986.
| -rw-r--r-- | addons/survey/__manifest__.py | 6 | ||||
| -rw-r--r-- | addons/survey/models/survey_survey.py | 3 | ||||
| -rw-r--r-- | addons/survey/models/survey_user.py | 26 | ||||
| -rw-r--r-- | addons/survey/tests/test_survey_invite.py | 161 | ||||
| -rw-r--r-- | addons/survey/views/survey_user_views.xml | 5 | ||||
| -rw-r--r-- | addons/survey/wizard/survey_invite.py | 311 | ||||
| -rw-r--r-- | addons/survey/wizard/survey_invite_views.xml | 64 |
7 files changed, 364 insertions, 212 deletions
diff --git a/addons/survey/__manifest__.py b/addons/survey/__manifest__.py index 9c2617434ac..91e92eb3c28 100644 --- a/addons/survey/__manifest__.py +++ b/addons/survey/__manifest__.py @@ -16,7 +16,11 @@ sent mails with personal token for the invitation of the survey. """, 'summary': 'Create surveys and analyze answers', 'website': 'https://www.odoo.com/page/survey', - 'depends': ['http_routing', 'mail', 'web_tour'], + 'depends': [ + 'auth_signup', + 'http_routing', + 'mail', + 'web_tour'], 'data': [ 'data/mail_template_data.xml', 'data/survey_data.xml', diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py index 0f0f788f029..ab4ad34e15f 100644 --- a/addons/survey/models/survey_survey.py +++ b/addons/survey/models/survey_survey.py @@ -345,12 +345,9 @@ class Survey(models.Model): local_context = dict( self.env.context, - default_model='survey.survey', - default_res_id=self.id, default_survey_id=self.id, default_use_template=bool(template), default_template_id=template and template.id or False, - default_composition_mode='comment', notif_layout='mail.mail_notification_light', ) return { diff --git a/addons/survey/models/survey_user.py b/addons/survey/models/survey_user.py index 21e5cfff446..d034098692b 100644 --- a/addons/survey/models/survey_user.py +++ b/addons/survey/models/survey_user.py @@ -45,7 +45,7 @@ class SurveyUserInput(models.Model): email = fields.Char('E-mail', readonly=True) # answers user_input_line_ids = fields.One2many('survey.user_input_line', 'user_input_id', string='Answers', copy=True) - deadline = fields.Datetime('Deadline', help="Date by which the person can open the survey and submit answers") + deadline = fields.Datetime('Deadline', help="Datetime until customer can open the survey and submit answers") last_displayed_page_id = fields.Many2one('survey.page', string='Last displayed page') quizz_score = fields.Float("Score for the quiz", compute="_compute_quizz_score", default=0.0) @@ -69,16 +69,20 @@ class SurveyUserInput(models.Model): ('create_date', '<', an_hour_ago)]).unlink() @api.multi - def action_survey_resend(self): - """ Send again the invitation """ - self.ensure_one() - local_context = { - 'survey_resent_token': True, - 'default_partner_ids': self.partner_id and [self.partner_id.id] or [], - 'default_multi_email': self.email or "", - 'default_public': 'email_private', - } - return self.survey_id.with_context(local_context).action_send_survey() + def action_resend(self): + partners = self.env['res.partner'] + emails = [] + for user_answer in self: + if user_answer.partner_id: + partners |= user_answer.partner_id + elif user_answer.email: + emails.append(user_answer.email) + + return self.survey_id.with_context( + default_existing_mode='resend', + default_partner_ids=partners.ids, + default_emails=','.join(emails) + ).action_send_survey() @api.multi def action_print_answers(self): diff --git a/addons/survey/tests/test_survey_invite.py b/addons/survey/tests/test_survey_invite.py index b2f464dcf98..92c74bc89a8 100644 --- a/addons/survey/tests/test_survey_invite.py +++ b/addons/survey/tests/test_survey_invite.py @@ -1,13 +1,24 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from datetime import datetime +from dateutil.relativedelta import relativedelta + +from odoo import fields from odoo.addons.survey.tests import common from odoo.exceptions import UserError +from odoo.tests import Form from odoo.tests.common import users class TestSurveyInvite(common.SurveyCase): + def setUp(self): + res = super(TestSurveyInvite, self).setUp() + # by default signup not allowed + self.env["ir.config_parameter"].set_param('auth_signup.invitation_scope', 'b2b') + return res + @users('survey_manager') def test_survey_invite_action(self): # Check correctly configured survey returns an invite wizard action @@ -30,3 +41,153 @@ class TestSurveyInvite(common.SurveyCase): for survey in surveys: with self.assertRaises(UserError): survey.action_send_survey() + + @users('survey_manager') + def test_survey_invite(self): + Answer = self.env['survey.user_input'] + deadline = fields.Datetime.now() + relativedelta(months=1) + + self.survey.write({'access_mode': 'public'}) + action = self.survey.action_send_survey() + invite_form = Form(self.env[action['res_model']].with_context(action['context'])) + + # some lowlevel checks that action is correctly configured + self.assertEqual(Answer.search([('survey_id', '=', self.survey.id)]), self.env['survey.user_input']) + self.assertEqual(invite_form.survey_id, self.survey) + + invite_form.partner_ids.add(self.customer) + invite_form.deadline = fields.Datetime.to_string(deadline) + + invite = invite_form.save() + invite.action_invite() + + answers = Answer.search([('survey_id', '=', self.survey.id)]) + self.assertEqual(len(answers), 1) + self.assertEqual( + set(answers.mapped('email')), + set([self.customer.email])) + self.assertEqual(answers.mapped('partner_id'), self.customer) + self.assertEqual(set(answers.mapped('deadline')), set([deadline])) + + @users('survey_manager') + def test_survey_invite_authentication_nosignup(self): + Answer = self.env['survey.user_input'] + + self.survey.write({'access_mode': 'authentication'}) + action = self.survey.action_send_survey() + invite_form = Form(self.env[action['res_model']].with_context(action['context'])) + + with self.assertRaises(UserError): # do not allow to add customer (partner without user) + invite_form.partner_ids.add(self.customer) + invite_form.partner_ids.clear() + invite_form.partner_ids.add(self.user_portal.partner_id) + invite_form.partner_ids.add(self.user_emp.partner_id) + with self.assertRaises(UserError): + invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>' + invite_form.emails = False + + invite = invite_form.save() + invite.action_invite() + + answers = Answer.search([('survey_id', '=', self.survey.id)]) + self.assertEqual(len(answers), 2) + self.assertEqual( + set(answers.mapped('email')), + set([self.user_emp.email, self.user_portal.email])) + self.assertEqual(answers.mapped('partner_id'), self.user_emp.partner_id | self.user_portal.partner_id) + + @users('survey_manager') + def test_survey_invite_authentication_signup(self): + self.env["ir.config_parameter"].sudo().set_param('auth_signup.invitation_scope', 'b2c') + self.survey.invalidate_cache() + Answer = self.env['survey.user_input'] + + self.survey.write({'access_mode': 'authentication'}) + action = self.survey.action_send_survey() + invite_form = Form(self.env[action['res_model']].with_context(action['context'])) + + invite_form.partner_ids.add(self.customer) + invite_form.partner_ids.add(self.user_portal.partner_id) + invite_form.partner_ids.add(self.user_emp.partner_id) + # TDE FIXME: not sure for emails in authentication + signup + # invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>' + + invite = invite_form.save() + invite.action_invite() + + answers = Answer.search([('survey_id', '=', self.survey.id)]) + self.assertEqual(len(answers), 3) + self.assertEqual( + set(answers.mapped('email')), + set([self.customer.email, self.user_emp.email, self.user_portal.email])) + self.assertEqual(answers.mapped('partner_id'), self.customer | self.user_emp.partner_id | self.user_portal.partner_id) + + @users('survey_manager') + def test_survey_invite_internal(self): + Answer = self.env['survey.user_input'] + + self.survey.write({'access_mode': 'internal'}) + action = self.survey.action_send_survey() + invite_form = Form(self.env[action['res_model']].with_context(action['context'])) + + with self.assertRaises(UserError): # do not allow to add customer (partner without user) + invite_form.partner_ids.add(self.customer) + with self.assertRaises(UserError): # do not allow to add portal user + invite_form.partner_ids.add(self.user_portal.partner_id) + invite_form.partner_ids.clear() + invite_form.partner_ids.add(self.user_emp.partner_id) + with self.assertRaises(UserError): + invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>' + invite_form.emails = False + + invite = invite_form.save() + invite.action_invite() + + answers = Answer.search([('survey_id', '=', self.survey.id)]) + self.assertEqual(len(answers), 1) + self.assertEqual( + set(answers.mapped('email')), + set([self.user_emp.email])) + self.assertEqual(answers.mapped('partner_id'), self.user_emp.partner_id) + + @users('survey_manager') + def test_survey_invite_public(self): + Answer = self.env['survey.user_input'] + + self.survey.write({'access_mode': 'public'}) + action = self.survey.action_send_survey() + invite_form = Form(self.env[action['res_model']].with_context(action['context'])) + + invite_form.partner_ids.add(self.customer) + invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>' + + invite = invite_form.save() + invite.action_invite() + + answers = Answer.search([('survey_id', '=', self.survey.id)]) + self.assertEqual(len(answers), 3) + self.assertEqual( + set(answers.mapped('email')), + set(['test1@example.com', 'Raoulette Vignolette <test2@example.com>', self.customer.email])) + self.assertEqual(answers.mapped('partner_id'), self.customer) + + @users('survey_manager') + def test_survey_invite_token(self): + Answer = self.env['survey.user_input'] + + self.survey.write({'access_mode': 'token'}) + action = self.survey.action_send_survey() + invite_form = Form(self.env[action['res_model']].with_context(action['context'])) + + invite_form.partner_ids.add(self.customer) + invite_form.emails = 'test1@example.com, Raoulette Vignolette <test2@example.com>' + + invite = invite_form.save() + invite.action_invite() + + answers = Answer.search([('survey_id', '=', self.survey.id)]) + self.assertEqual(len(answers), 3) + self.assertEqual( + set(answers.mapped('email')), + set(['test1@example.com', 'Raoulette Vignolette <test2@example.com>', self.customer.email])) + self.assertEqual(answers.mapped('partner_id'), self.customer) diff --git a/addons/survey/views/survey_user_views.xml b/addons/survey/views/survey_user_views.xml index 5344823ed42..dfa5aa4aa0b 100644 --- a/addons/survey/views/survey_user_views.xml +++ b/addons/survey/views/survey_user_views.xml @@ -29,8 +29,9 @@ <field name="arch" type="xml"> <form string="Survey User inputs" create="false"> <header> - <button name="action_survey_resend" string="Send Invitation Again" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('input_type','=','manually'), ('state', '=', 'done')]}"/> - <button name="action_print_answers" states="done" string="Print" type="object" class="oe_highlight" /> + <button name="action_resend" string="Resend Invitation" type="object" class="oe_highlight" + attrs="{'invisible': ['|', ('state', '=', 'done'), '&', ('partner_id', '=', False), ('email', '=', False)]}"/> + <button name="action_print_answers" states="done" string="Print" type="object" class="oe_highlight"/> <field name="state" widget="statusbar"/> </header> <sheet> diff --git a/addons/survey/wizard/survey_invite.py b/addons/survey/wizard/survey_invite.py index 9190d67b6b7..0811ea67ee6 100644 --- a/addons/survey/wizard/survey_invite.py +++ b/addons/survey/wizard/survey_invite.py @@ -3,29 +3,21 @@ import logging import re -import uuid from email.utils import formataddr -from werkzeug import urls -from odoo import api, fields, models, _ +from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) emails_split = re.compile(r"[;,\n\r]+") -email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+") class SurveyInvite(models.TransientModel): _name = 'survey.invite' _description = 'Survey Invitation Wizard' - def default_survey_id(self): - context = self.env.context - if context.get('model') == 'survey.survey': - return context.get('res_id') - @api.model def _get_default_from(self): if self.env.user.email: @@ -52,52 +44,69 @@ class SurveyInvite(models.TransientModel): ondelete='set null', default=_get_default_author, help="Author of the message.") # recipients - partner_ids = fields.Many2many('res.partner', 'survey_mail_compose_message_res_partner_rel', 'wizard_id', 'partner_id', string='Existing contacts') - multi_email = fields.Text(string='List of emails', help="This list of emails of recipients will not be converted in contacts.\ + partner_ids = fields.Many2many( + 'res.partner', 'survey_mail_compose_message_res_partner_rel', 'wizard_id', 'partner_id', string='Recipients') + emails = fields.Text(string='Additional emails', help="This list of emails of recipients will not be converted in contacts.\ Emails must be separated by commas, semicolons or newline.") + existing_mode = fields.Selection([ + ('skip', 'Skip'), ('resend', 'Resend'), ('prevent', 'Prevent')], + string='Existing', default='skip') # technical info mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server') # survey - survey_id = fields.Many2one('survey.survey', string='Survey', default=default_survey_id, required=True) - public = fields.Selection([('public_link', 'Share the public web link to your audience.'), - ('email_public_link', 'Send by email the public web link to your audience.'), - ('email_private', 'Send private invitation to your audience (only one response per recipient and per invitation).')], - string='Share options', default='public_link', required=True) - public_url = fields.Char(compute="_compute_survey_url", string="Public url") - date_deadline = fields.Date(string="Deadline to which the invitation to respond is valid", - help="Deadline to which the invitation to respond for this survey is valid. If the field is empty,\ - the invitation is still valid.") - - @api.depends('survey_id') - def _compute_survey_url(self): - for wizard in self: - wizard.public_url = wizard.survey_id.public_url - - @api.model - def default_get(self, fields): - res = super(SurveyInvite, self).default_get(fields) - context = self.env.context - if context.get('active_model') == 'res.partner' and context.get('active_ids'): - res.update({'partner_ids': context['active_ids']}) - return res - - @api.onchange('multi_email') - def onchange_multi_email(self): - emails = list(set(emails_split.split(self.multi_email or ""))) - emails_checked = [] - error_message = "" + survey_id = fields.Many2one('survey.survey', string='Survey', required=True) + survey_url = fields.Char(related="survey_id.public_url", readonly=True) + survey_access_mode = fields.Selection(related="survey_id.access_mode", readonly=True) + deadline = fields.Datetime(string="Answer deadline") + + @api.onchange('emails') + def _onchange_emails(self): + if self.emails and (self.survey_access_mode == 'internal' or (self.survey_access_mode == 'authentication' and not self.survey_id.users_can_signup)): + raise UserError(_('This survey does not allow external people to participate. You should create user accounts or update survey access mode accordingly.')) + if not self.emails: + return + valid, error = [], [] + emails = list(set(emails_split.split(self.emails or ""))) for email in emails: - email = email.strip() - if email: - if not email_validator.match(email): - error_message += "\n'%s'" % email - else: - emails_checked.append(email) - if error_message: - raise UserError(_("Incorrect Email Address: %s") % error_message) - - emails_checked.sort() - self.multi_email = '\n'.join(emails_checked) + email_check = tools.email_split_and_format(email) + if not email_check: + error.append(email) + else: + valid.extend(email_check) + if error: + raise UserError(_("Some emails you just entered are incorrect: %s") % (', '.join(error))) + self.emails = '\n'.join(valid) + + @api.onchange('survey_access_mode') + def _onchange_survey_access_mode(self): + if self.survey_access_mode == 'internal': + return {'domain': { + 'partner_ids': [('user_ids.groups_id', 'in', [self.env.ref('base.group_user').id])] + }} + elif self.survey_access_mode == 'authentication': + if not self.survey_id.users_can_signup: + return {'domain': { + 'partner_ids': [('user_ids.groups_id', 'in', [self.env.ref('base.group_user').id, self.env.ref('base.group_portal').id])] + }} + + @api.onchange('partner_ids') + def _onchange_partner_ids(self): + if self.survey_access_mode == 'internal' and self.partner_ids: + invalid_partners = self.partner_ids.filtered(lambda partner: not partner.user_ids or not all(user.has_group('base.group_user') for user in partner.user_ids)) + if invalid_partners: + raise UserError( + _('The following recipients are not valid users belonging to the employee group: %s. You should create user accounts for them.' % + (','.join(invalid_partners.mapped('name'))))) + elif self.survey_access_mode == 'authentication' and self.partner_ids: + if not self.survey_id.users_can_signup: + invalid_partners = self.env['res.partner'].search([ + ('user_ids', '=', False), + ('id', 'in', self.partner_ids.ids) + ]) + if invalid_partners: + raise UserError( + _('The following recipients have no user account: %s. You should create user accounts for them or allow external signup in configuration.' % + (','.join(invalid_partners.mapped('name'))))) @api.onchange('template_id', 'survey_id') def _onchange_template_id(self): @@ -146,119 +155,109 @@ class SurveyInvite(models.TransientModel): # Wizard validation and send #------------------------------------------------------ - @api.multi - def send_mail_action(self): - return self.send_mail() + def _prepare_answers(self, partners, emails): + answers = self.env['survey.user_input'] + existing_answers = self.env['survey.user_input'].search([ + '&', ('survey_id', '=', self.survey_id.id), + '|', + ('partner_id', 'in', partners.ids), + ('email', 'in', emails) + ]) + partners_done = self.env['res.partner'] + emails_done = [] + if existing_answers: + if self.existing_mode == 'prevent': + raise UserError(_('Some of recipients already started survey. Please update recipients list accordingly.')) + + if self.existing_mode in ['skip', 'reset']: + partners_done = existing_answers.mapped('partner_id') + emails_done = existing_answers.mapped('email') + + if self.existing_mode == 'reset': + existing_answers.write({ + 'deadline': self.deadline, + 'state': 'new', + 'user_input_line_ids': [(5, 0)], + }) + answers |= existing_answers + + for new_partner in partners - partners_done: + answers |= self.survey_id._create_answer(partner=new_partner, deadline=self.deadline) + for new_email in [email for email in emails if email not in emails_done]: + answers |= self.survey_id._create_answer(email=new_email, deadline=self.deadline) + + return answers + + def _send_mail(self, answer): + """ Create mail specific for recipient containing notably its access token """ + url = '%s?token=%s' % (self.survey_id.public_url, answer.token) + + # post the message + mail_values = { + 'email_from': self.email_from, + 'author_id': self.author_id.id, + 'model': None, + 'res_id': None, + 'subject': self.subject, + 'body_html': self.body.replace("__URL__", url), + 'attachment_ids': [(4, att.id) for att in self.attachment_ids], + 'auto_delete': True, + } + if answer.partner_id: + mail_values['recipient_ids'] = [(4, answer.partner_id.id)] + else: + mail_values['email_to'] = answer.email + + # optional support of notif_layout in context + notif_layout = self.env.context.get('notif_layout', self.env.context.get('custom_layout')) + if notif_layout: + try: + template = self.env.ref(notif_layout, raise_if_not_found=True) + except ValueError: + _logger.warning('QWeb template %s not found when sending survey mails. Sending without layouting.' % (notif_layout)) + else: + template_ctx = { + 'message': self.env['mail.message'].sudo().new(dict(body=mail_values['body_html'], record_name=self.survey_id.title)), + 'model_description': self.env['ir.model']._get('survey.survey').display_name, + 'company': self.env.user.company_id, + } + body = template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True) + mail_values['body_html'] = self.env['mail.thread']._replace_local_links(body) + + return self.env['mail.mail'].sudo().create(mail_values) @api.multi - def send_mail(self, auto_commit=False): + def action_invite(self): """ Process the wizard content and proceed with sending the related email(s), rendering any template patterns on the fly if needed """ - - SurveyUserInput = self.env['survey.user_input'] + self.ensure_one() Partner = self.env['res.partner'] - Mail = self.env['mail.mail'] - notif_layout = self.env.context.get('notif_layout', self.env.context.get('custom_layout')) - def create_response_and_send_mail(wizard, token, partner_id, email): - """ Create one mail by recipients and replace __URL__ by link with identification token """ - #set url - url = wizard.survey_id.public_url - - if token: - url = url + '/' + token - - # post the message - values = { - 'model': None, - 'res_id': None, - 'subject': wizard.subject, - 'body': wizard.body.replace("__URL__", url), - 'body_html': wizard.body.replace("__URL__", url), - 'parent_id': None, - 'attachment_ids': wizard.attachment_ids and [(6, 0, wizard.attachment_ids.ids)] or None, - 'email_from': wizard.email_from or None, - 'auto_delete': True, - } - if partner_id: - values['recipient_ids'] = [(4, partner_id)] - else: - values['email_to'] = email - - # optional support of notif_layout in context - if notif_layout: - try: - template = self.env.ref(notif_layout, raise_if_not_found=True) - except ValueError: - _logger.warning('QWeb template %s not found when sending survey mails. Sending without layouting.' % (notif_layout)) - else: - template_ctx = { - 'message': self.env['mail.message'].sudo().new(dict(body=values['body_html'], record_name=wizard.survey_id.title)), - 'model_description': self.env['ir.model']._get('survey.survey').display_name, - 'company': self.env.user.company_id, - } - body = template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True) - values['body_html'] = self.env['mail.thread']._replace_local_links(body) - - Mail.create(values).send() - - def create_token(wizard, partner_id, email): - if context.get("survey_resent_token"): - survey_user_input = SurveyUserInput.search([('survey_id', '=', wizard.survey_id.id), - ('state', 'in', ['new', 'skip']), '|', ('partner_id', '=', partner_id), - ('email', '=', email)], limit=1) - if survey_user_input: - return survey_user_input.token - if wizard.public != 'email_private': - return None + # check if __URL__ is in the text + if self.body.find("__URL__") < 0: + raise UserError(_("The content of the text don't contain '__URL__'. \ + __URL__ is automaticaly converted into the special url of the survey.")) + + # compute partners and emails, try to find partners for given emails + valid_partners = self.partner_ids + valid_emails = [] + for email in emails_split.split(self.emails or ''): + partner = False + email_normalized = tools.email_normalize(email) + if email_normalized: + partner = Partner.search([('email_normalized', '=', email_normalized)]) + if partner: + valid_partners |= partner else: - token = str(uuid.uuid4()) - # create response with token - survey_user_input = SurveyUserInput.create({ - 'survey_id': wizard.survey_id.id, - 'deadline': wizard.date_deadline, - 'input_type': 'link', - 'state': 'new', - 'token': token, - 'partner_id': partner_id, - 'email': email}) - return survey_user_input.token + email_formatted = tools.email_split_and_format(email) + if email_formatted: + valid_emails.extend(email_formatted) - for wizard in self: - # check if __URL__ is in the text - if wizard.body.find("__URL__") < 0: - raise UserError(_("The content of the text don't contain '__URL__'. \ - __URL__ is automaticaly converted into the special url of the survey.")) - - context = self.env.context - if not wizard.multi_email and not wizard.partner_ids and (context.get('default_partner_ids') or context.get('default_multi_email')): - wizard.multi_email = context.get('default_multi_email') - wizard.partner_ids = context.get('default_partner_ids') - - # quick check of email list - emails_list = [] - if wizard.multi_email: - emails = set(emails_split.split(wizard.multi_email)) - set(wizard.partner_ids.mapped('email')) - for email in emails: - email = email.strip() - if email_validator.match(email): - emails_list.append(email) - - # remove public anonymous access - partner_list = [] - for partner in wizard.partner_ids: - partner_list.append({'id': partner.id, 'email': partner.email}) - - if not len(emails_list) and not len(partner_list): - raise UserError(_("Please enter at least one valid recipient.")) - - for email in emails_list: - partner = Partner.search([('email', '=', email)], limit=1) - token = create_token(wizard, partner.id, email) - create_response_and_send_mail(wizard, token, partner.id, email) - - for partner in partner_list: - token = create_token(wizard, partner['id'], partner['email']) - create_response_and_send_mail(wizard, token, partner['id'], partner['email']) + if not valid_partners and not valid_emails: + raise UserError(_("Please enter at least one valid recipient.")) + + answers = self._prepare_answers(valid_partners, valid_emails) + for answer in answers: + self._send_mail(answer) return {'type': 'ir.actions.act_window_close'} diff --git a/addons/survey/wizard/survey_invite_views.xml b/addons/survey/wizard/survey_invite_views.xml index 2d0edef97e7..f9e19be04ee 100644 --- a/addons/survey/wizard/survey_invite_views.xml +++ b/addons/survey/wizard/survey_invite_views.xml @@ -1,60 +1,46 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> <data> - <!-- Composition form (context['survey_resent_token'], if True, hide share option, emails list and partner_ids) --> <record model="ir.ui.view" id="survey_invite_view_form"> <field name="name">survey.invite.view.form</field> <field name="model">survey.invite</field> <field name="arch" type="xml"> <form string="Compose Email"> - <group col="2"> - <field name="survey_id" readonly="context.get('default_survey_id')"/> - <field name="public" widget="radio" invisible="context.get('survey_resent_token')" /> - </group> - <group col="1" invisible="context.get('survey_resent_token')" attrs="{'invisible':[('public','in',['email_private', 'email_public_link'])]}" class="oe_survey_email_public"> - <div> - You can share your survey web public link and/or send private invitations to your audience. People can answer once per invitation, and whenever they want with the public web link (in this case, the "Login Required" setting must be disabled). - </div> - <div>Copy, paste and share the web link below to your audience.</div> - <code><field name="public_url"/></code> - </group> - <group col="1" attrs="{'invisible':['|',('public','=',False),('public','in',['public_link'])]}"> + <group col="1"> <group col="2"> + <field name="survey_access_mode" invisible="1"/> + <field name="survey_id" readonly="context.get('default_survey_id')"/> + <field name="existing_mode" widget="radio" invisible="1" /> + <field name="survey_url" label="Public share URL" readonly="1" widget="CopyClipboardText" + attrs="{'invisible':[('survey_access_mode', '!=', 'public')]}" + class="mb16"/> <field name="partner_ids" - invisible="context.get('survey_resent_token')" + attrs="{'readonly': [('existing_mode', '=', 'resend')]}" widget="many2many_tags_email" placeholder="Add existing contacts..." - context="{'force_email':True, 'show_email':True}" /> - <field name="multi_email" - invisible="context.get('survey_resent_token')" + context="{'force_email':True, 'show_email':True, 'no_create_edit': True}"/> + <field name="emails" + attrs="{ + 'invisible': [('survey_access_mode', 'in', ['authentication', 'internal'])], + 'readonly': [('existing_mode', '=', 'resend')] + }" placeholder="Add a list of email of recipients (will not be converted into contacts). Separated by commas, semicolons or newline..."/> <field name="subject" placeholder="Subject..."/> </group> <field name="body" options="{'style-inline': true}"/> - <group col="2"> - <div> - <label for="attachment_ids" /> - <field name="attachment_ids" widget="many2many_binary" /> - </div> - <div class="oe_survey_date_deadline"> - <label for="date_deadline" /> - <field name="date_deadline" /> - </div> + <group> + <group> + <field name="attachment_ids" widget="many2many_binary"/> + </group> + <group> + <field name="deadline"/> + <field name="template_id" label="Use template"/> + </group> </group> </group> - <footer attrs="{'invisible':[('public','in',['email_private', 'email_public_link'])]}"> - <button string="Close" class="btn-primary" special="cancel" /> - </footer> - <footer attrs="{'invisible':['|',('public','=',False),('public','in',['public_link'])]}"> - <button string="Send" name="send_mail_action" type="object" class="btn-primary" /> - <button string="Cancel" class="btn-secondary" special="cancel" /> - <group class="oe_right" col="1"> - <div> - Use template - <field name="template_id" nolabel="1"/> - </div> - <button icon="/email_template/static/src/img/email_template_save.png" type="object" name="save_as_template" string="Save as new template" class="oe_link" help="Save as a new template" /> - </group> + <footer> + <button string="Send" name="action_invite" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> </footer> </form> </field> |
