[IMP] survey: add invite_token to user_input to reset the attempts count
Task #1902306 Purpose ======= When you invite people to take a certification multiple times, the user will have his "attempts" count reset with every invitation. This is important when there is a limited number of attempts available. ex: A user invited twice will have 2 x allowed_attempts_count to succeed the survey. Attempts are now separated in different pools using a "invite_token" field on the survey.user_input.
This commit is contained in:
@@ -165,7 +165,10 @@ class Survey(http.Controller):
|
||||
return werkzeug.utils.redirect("/")
|
||||
|
||||
try:
|
||||
retry_answer_sudo = survey_sudo._create_answer(user=request.env.user, partner=answer_sudo.partner_id, email=answer_sudo.email)
|
||||
retry_answer_sudo = survey_sudo._create_answer(user=request.env.user, partner=answer_sudo.partner_id, email=answer_sudo.email, invite_token=answer_sudo.invite_token, **{
|
||||
'input_type': answer_sudo.input_type,
|
||||
'deadline': answer_sudo.deadline,
|
||||
})
|
||||
except:
|
||||
return werkzeug.utils.redirect("/")
|
||||
return request.redirect('/survey/start/%s?%s' % (survey_sudo.access_token, keep_query('*', answer_token=retry_answer_sudo.token)))
|
||||
@@ -356,7 +359,7 @@ class Survey(http.Controller):
|
||||
return {}
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
if not survey_sudo._has_attempts_left(answer_sudo.partner_id, answer_sudo.email):
|
||||
if not survey_sudo._has_attempts_left(answer_sudo.partner_id, answer_sudo.email, answer_sudo.invite_token):
|
||||
# prevent cheating with users creating multiple 'user_input' before their last attempt
|
||||
return {}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ class Survey(models.Model):
|
||||
return super(Survey, self).copy_data(default)
|
||||
|
||||
@api.multi
|
||||
def _create_answer(self, user=False, partner=False, email=False, test_entry=False, **additional_vals):
|
||||
def _create_answer(self, user=False, partner=False, email=False, test_entry=False, check_attempts=True, **additional_vals):
|
||||
""" Main entry point to get a token back or create a new one. This method
|
||||
does check for current user access in order to explicitely validate
|
||||
security.
|
||||
@@ -188,7 +188,8 @@ class Survey(models.Model):
|
||||
if partner and not user and partner.user_ids:
|
||||
user = partner.user_ids[0]
|
||||
|
||||
survey._check_answer_creation(user, partner, email, test_entry=test_entry)
|
||||
invite_token = additional_vals.pop('invite_token', False)
|
||||
survey._check_answer_creation(user, partner, email, test_entry=test_entry, check_attempts=check_attempts, invite_token=invite_token)
|
||||
answer_vals = {
|
||||
'survey_id': survey.id,
|
||||
'test_entry': test_entry,
|
||||
@@ -203,13 +204,21 @@ class Survey(models.Model):
|
||||
else:
|
||||
answer_vals['email'] = email
|
||||
|
||||
if invite_token:
|
||||
answer_vals['invite_token'] = invite_token
|
||||
elif survey.is_attempts_limited and survey.access_mode != 'public':
|
||||
# attempts limited: create a new invite_token
|
||||
# exception made for 'public' access_mode since the attempts pool is global because answers are
|
||||
# created every time the user lands on '/start'
|
||||
answer_vals['invite_token'] = self.env['survey.user_input']._generate_invite_token()
|
||||
|
||||
answer_vals.update(additional_vals)
|
||||
answers += answers.create(answer_vals)
|
||||
|
||||
return answers
|
||||
|
||||
@api.multi
|
||||
def _check_answer_creation(self, user, partner, email, test_entry=False):
|
||||
def _check_answer_creation(self, user, partner, email, test_entry=False, check_attempts=True, invite_token=False):
|
||||
""" Ensure conditions to create new tokens are met. """
|
||||
self.ensure_one()
|
||||
if test_entry:
|
||||
@@ -229,7 +238,7 @@ class Survey(models.Model):
|
||||
raise UserError(_('Creating token for external people is not allowed for surveys requesting authentication.'))
|
||||
if self.access_mode == 'internal' and (not user or not user.has_group('base.group_user')):
|
||||
raise UserError(_('Creating token for anybody else than employees is not allowed for internal surveys.'))
|
||||
if not self._has_attempts_left(partner or (user and user.partner_id), email):
|
||||
if check_attempts and not self._has_attempts_left(partner or (user and user.partner_id), email, invite_token):
|
||||
raise UserError(_('No attempts left.'))
|
||||
|
||||
@api.model
|
||||
@@ -505,16 +514,16 @@ class Survey(models.Model):
|
||||
return action
|
||||
|
||||
@api.multi
|
||||
def _has_attempts_left(self, partner, email):
|
||||
def _has_attempts_left(self, partner, email, invite_token):
|
||||
self.ensure_one()
|
||||
|
||||
if (self.access_mode != 'public' or self.users_login_required) and self.is_attempts_limited:
|
||||
return self._get_number_of_attempts_lefts(partner, email) > 0
|
||||
return self._get_number_of_attempts_lefts(partner, email, invite_token) > 0
|
||||
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _get_number_of_attempts_lefts(self, partner, email):
|
||||
def _get_number_of_attempts_lefts(self, partner, email, invite_token):
|
||||
""" Returns the number of attempts left. """
|
||||
self.ensure_one()
|
||||
|
||||
@@ -529,6 +538,9 @@ class Survey(models.Model):
|
||||
else:
|
||||
domain = expression.AND([domain, [('email', '=', email)]])
|
||||
|
||||
if invite_token:
|
||||
domain = expression.AND([domain, [('invite_token', '=', invite_token)]])
|
||||
|
||||
return self.attempts_limit - self.env['survey.user_input'].search_count(domain)
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -45,6 +45,8 @@ class SurveyUserInput(models.Model):
|
||||
test_entry = fields.Boolean(readonly=True)
|
||||
# identification and access
|
||||
token = fields.Char('Identification token', default=lambda self: str(uuid.uuid4()), readonly=True, required=True, copy=False)
|
||||
# no unique constraint, as it identifies a pool of attempts
|
||||
invite_token = fields.Char('Invite token', readonly=True, copy=False)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
|
||||
email = fields.Char('E-mail', readonly=True)
|
||||
|
||||
@@ -95,6 +97,10 @@ class SurveyUserInput(models.Model):
|
||||
('state', '=', 'new'),
|
||||
('create_date', '<', an_hour_ago)]).unlink()
|
||||
|
||||
@api.model
|
||||
def _generate_invite_token(self):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@api.multi
|
||||
def action_resend(self):
|
||||
partners = self.env['res.partner']
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<t t-else="">
|
||||
<div>Unfortunately, you have failed the test.</div>
|
||||
<t t-if="survey.is_attempts_limited">
|
||||
<t t-set="attempts_left" t-value="survey._get_number_of_attempts_lefts(answer.partner_id, answer.email)" />
|
||||
<t t-set="attempts_left" t-value="survey._get_number_of_attempts_lefts(answer.partner_id, answer.email, answer.invite_token)" />
|
||||
<t t-if="attempts_left > 0">
|
||||
<p><span>Number of attemps left</span>: <span t-esc="attempts_left"></span></p>
|
||||
<p>
|
||||
|
||||
@@ -170,12 +170,23 @@ class SurveyInvite(models.TransientModel):
|
||||
if self.existing_mode == 'resend':
|
||||
partners_done = existing_answers.mapped('partner_id')
|
||||
emails_done = existing_answers.mapped('email')
|
||||
answers |= existing_answers
|
||||
|
||||
# only add the last answer for each user of each type (partner_id & email)
|
||||
# to have only one mail sent per user
|
||||
for partner_done in partners_done:
|
||||
answers |= next(existing_answer for existing_answer in
|
||||
existing_answers.sorted(lambda answer: answer.create_date, reverse=True)
|
||||
if existing_answer.partner_id == partner_done)
|
||||
|
||||
for email_done in emails_done:
|
||||
answers |= next(existing_answer for existing_answer in
|
||||
existing_answers.sorted(lambda answer: answer.create_date, reverse=True)
|
||||
if existing_answer.email == email_done)
|
||||
|
||||
for new_partner in partners - partners_done:
|
||||
answers |= self.survey_id._create_answer(partner=new_partner, **self._get_answers_values())
|
||||
answers |= self.survey_id._create_answer(partner=new_partner, check_attempts=False, **self._get_answers_values())
|
||||
for new_email in [email for email in emails if email not in emails_done]:
|
||||
answers |= self.survey_id._create_answer(email=new_email, **self._get_answers_values())
|
||||
answers |= self.survey_id._create_answer(email=new_email, check_attempts=False, **self._get_answers_values())
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user