[IMP] survey: reorder some code to ease understanding
PURPOSE Perform a back2basics review of survey to improve daily use, new user onboarding and labelling. SPECIFICATIONS Reorder some code to better understand module organization. LINKS Task 2039068 PR #36229
This commit is contained in:
@@ -20,6 +20,10 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class Survey(http.Controller):
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACCESS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _fetch_from_access_token(self, survey_token, answer_token):
|
||||
""" Check that given token matches an answer from the given survey_id.
|
||||
Returns a sudo-ed browse record of survey in order to avoid access rights
|
||||
@@ -182,6 +186,10 @@ class Survey(http.Controller):
|
||||
'deadline': answer.deadline,
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TAKING SURVEY ROUTES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route('/survey/start/<string:survey_token>', type='http', auth='public', website=True)
|
||||
def survey_start(self, survey_token, answer_token=None, email=False, **post):
|
||||
""" Start a survey by providing
|
||||
@@ -423,6 +431,10 @@ class Survey(http.Controller):
|
||||
|
||||
return json.dumps(ret)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# COMPLETED SURVEY ROUTES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route('/survey/print/<string:survey_token>', type='http', auth='public', website=True)
|
||||
def survey_print(self, survey_token, review=False, answer_token=None, **post):
|
||||
'''Display an survey in printable view; if <answer_token> is set, it will
|
||||
|
||||
@@ -8,7 +8,7 @@ from itertools import product
|
||||
from werkzeug import urls
|
||||
import random
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
|
||||
@@ -188,7 +188,10 @@ class Survey(models.Model):
|
||||
if not self.users_login_required or not self.certificate:
|
||||
self.certification_give_badge = False
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
survey = super(Survey, self).create(vals)
|
||||
@@ -202,12 +205,15 @@ class Survey(models.Model):
|
||||
return self.sudo()._handle_certification_badges(vals)
|
||||
return result
|
||||
|
||||
# Public methods #
|
||||
def copy_data(self, default=None):
|
||||
title = _("%s (copy)") % (self.title)
|
||||
default = dict(default or {}, title=title)
|
||||
return super(Survey, self).copy_data(default)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TECHNICAL
|
||||
# ------------------------------------------------------------
|
||||
|
||||
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
|
||||
@@ -278,6 +284,60 @@ class Survey(models.Model):
|
||||
if check_attempts and not self._has_attempts_left(partner or (user and user.partner_id), email, invite_token):
|
||||
raise UserError(_('No attempts left.'))
|
||||
|
||||
def _prepare_answer_questions(self):
|
||||
""" Will generate the questions for a randomized survey.
|
||||
It uses the random_questions_count of every sections of the survey to
|
||||
pick a random number of questions and returns the merged recordset """
|
||||
self.ensure_one()
|
||||
|
||||
questions = self.env['survey.question']
|
||||
|
||||
for page in self.page_ids:
|
||||
if self.questions_selection == 'all':
|
||||
questions |= page.question_ids
|
||||
else:
|
||||
if page.random_questions_count > 0 and len(page.question_ids) > page.random_questions_count:
|
||||
questions = questions.concat(*random.sample(page.question_ids, page.random_questions_count))
|
||||
else:
|
||||
questions |= page.question_ids
|
||||
|
||||
if not questions:
|
||||
questions = self.question_ids
|
||||
|
||||
return questions
|
||||
|
||||
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, invite_token) > 0
|
||||
|
||||
return True
|
||||
|
||||
def _get_number_of_attempts_lefts(self, partner, email, invite_token):
|
||||
""" Returns the number of attempts left. """
|
||||
self.ensure_one()
|
||||
|
||||
domain = [
|
||||
('survey_id', '=', self.id),
|
||||
('test_entry', '=', False),
|
||||
('state', '=', 'done')
|
||||
]
|
||||
|
||||
if partner:
|
||||
domain = expression.AND([domain, [('partner_id', '=', partner.id)]])
|
||||
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)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def next_page_or_question(self, user_input, page_or_question_id, go_back=False):
|
||||
""" The next page to display to the user, knowing that page_id is the id
|
||||
@@ -329,6 +389,115 @@ class Survey(models.Model):
|
||||
else:
|
||||
return (pages_or_questions[current_page_index + 1][1], False)
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
def action_open(self):
|
||||
self.write({'state': 'open'})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
|
||||
def action_start_survey(self):
|
||||
""" Open the website page with the survey form """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Start Survey",
|
||||
'target': 'self',
|
||||
'url': self.public_url + trail
|
||||
}
|
||||
|
||||
def action_send_survey(self):
|
||||
""" Open a window to compose an email, pre-filled with the survey message """
|
||||
# Ensure that this survey has at least one page with at least one question.
|
||||
if (not self.page_ids and self.questions_layout == 'page_per_section') or not self.question_ids:
|
||||
raise UserError(_('You cannot send an invitation for a survey that has no questions.'))
|
||||
|
||||
if self.state == 'closed':
|
||||
raise UserError(_("You cannot send invitations for closed surveys."))
|
||||
|
||||
template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
self.env.context,
|
||||
default_survey_id=self.id,
|
||||
default_use_template=bool(template),
|
||||
default_template_id=template and template.id or False,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'survey.invite',
|
||||
'target': 'new',
|
||||
'context': local_context,
|
||||
}
|
||||
|
||||
def action_print_survey(self):
|
||||
""" Open the website page with the survey printable view """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Print Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/print/%s%s' % (self.access_token, trail)
|
||||
}
|
||||
|
||||
def action_result_survey(self):
|
||||
""" Open the website page with the survey results view """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Results of the Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/results/%s' % self.id
|
||||
}
|
||||
|
||||
def action_test_survey(self):
|
||||
''' Open the website page with the survey form into test mode'''
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Test Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/test/%s' % self.access_token,
|
||||
}
|
||||
|
||||
def action_survey_user_input_completed(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_completed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input_certified(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_quizz_passed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0]})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GRAPH / RESULTS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def filter_input_ids(self, filters, finished=False):
|
||||
"""If user applies any filters, then this function returns list of
|
||||
filtered user_input_id and label's strings for display data in web.
|
||||
@@ -453,6 +622,10 @@ class Survey(models.Model):
|
||||
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GAMIFICATION / BADGES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _create_certification_badge_trigger(self):
|
||||
self.ensure_one()
|
||||
goal = self.env['gamification.goal.definition'].create({
|
||||
@@ -502,160 +675,3 @@ class Survey(models.Model):
|
||||
# delete all challenges and goals because not needed anymore (challenge lines are deleted in cascade)
|
||||
challenges_to_delete.unlink()
|
||||
goals_to_delete.unlink()
|
||||
|
||||
# Actions
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
def action_open(self):
|
||||
self.write({'state': 'open'})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
|
||||
def action_start_survey(self):
|
||||
""" Open the website page with the survey form """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Start Survey",
|
||||
'target': 'self',
|
||||
'url': self.public_url + trail
|
||||
}
|
||||
|
||||
def action_send_survey(self):
|
||||
""" Open a window to compose an email, pre-filled with the survey message """
|
||||
# Ensure that this survey has at least one page with at least one question.
|
||||
if (not self.page_ids and self.questions_layout == 'page_per_section') or not self.question_ids:
|
||||
raise UserError(_('You cannot send an invitation for a survey that has no questions.'))
|
||||
|
||||
if self.state == 'closed':
|
||||
raise UserError(_("You cannot send invitations for closed surveys."))
|
||||
|
||||
template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
self.env.context,
|
||||
default_survey_id=self.id,
|
||||
default_use_template=bool(template),
|
||||
default_template_id=template and template.id or False,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'survey.invite',
|
||||
'target': 'new',
|
||||
'context': local_context,
|
||||
}
|
||||
|
||||
def action_print_survey(self):
|
||||
""" Open the website page with the survey printable view """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Print Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/print/%s%s' % (self.access_token, trail)
|
||||
}
|
||||
|
||||
def action_result_survey(self):
|
||||
""" Open the website page with the survey results view """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Results of the Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/results/%s' % self.id
|
||||
}
|
||||
|
||||
def action_test_survey(self):
|
||||
''' Open the website page with the survey form into test mode'''
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Test Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/test/%s' % self.access_token,
|
||||
}
|
||||
|
||||
def action_survey_user_input_completed(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_completed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input_certified(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_quizz_passed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0]})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
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, invite_token) > 0
|
||||
|
||||
return True
|
||||
|
||||
def _get_number_of_attempts_lefts(self, partner, email, invite_token):
|
||||
""" Returns the number of attempts left. """
|
||||
self.ensure_one()
|
||||
|
||||
domain = [
|
||||
('survey_id', '=', self.id),
|
||||
('test_entry', '=', False),
|
||||
('state', '=', 'done')
|
||||
]
|
||||
|
||||
if partner:
|
||||
domain = expression.AND([domain, [('partner_id', '=', partner.id)]])
|
||||
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)
|
||||
|
||||
def _prepare_answer_questions(self):
|
||||
""" Will generate the questions for a randomized survey.
|
||||
It uses the random_questions_count of every sections of the survey to
|
||||
pick a random number of questions and returns the merged recordset """
|
||||
self.ensure_one()
|
||||
|
||||
questions = self.env['survey.question']
|
||||
|
||||
for page in self.page_ids:
|
||||
if self.questions_selection == 'all':
|
||||
questions |= page.question_ids
|
||||
else:
|
||||
if page.random_questions_count > 0 and len(page.question_ids) > page.random_questions_count:
|
||||
questions = questions.concat(*random.sample(page.question_ids, page.random_questions_count))
|
||||
else:
|
||||
questions |= page.question_ids
|
||||
|
||||
if not questions:
|
||||
questions = self.question_ids
|
||||
|
||||
return questions
|
||||
|
||||
@@ -200,10 +200,12 @@ class SurveyUserInputLine(models.Model):
|
||||
_description = 'Survey User Input Line'
|
||||
_rec_name = 'user_input_id'
|
||||
|
||||
# survey data
|
||||
user_input_id = fields.Many2one('survey.user_input', string='User Input', ondelete='cascade', required=True)
|
||||
survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False)
|
||||
question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True)
|
||||
page_id = fields.Many2one(related='question_id.page_id', string="Section", readonly=False)
|
||||
survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False)
|
||||
# answer
|
||||
skipped = fields.Boolean('Skipped')
|
||||
answer_type = fields.Selection([
|
||||
('text', 'Text'),
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- USER INPUTS -->
|
||||
<record id="survey_user_input_search" model="ir.ui.view">
|
||||
<field name="name">survey_user_input_search</field>
|
||||
<record id="survey_user_input_view_search" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.search</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Survey">
|
||||
@@ -26,8 +26,9 @@
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="survey_user_input_form">
|
||||
<field name="name">survey_user_input_form</field>
|
||||
|
||||
<record id="survey_user_input_view_form" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.form</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Survey User inputs" create="false">
|
||||
@@ -76,8 +77,9 @@
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="survey_user_input_tree">
|
||||
<field name="name">survey_user_input_tree</field>
|
||||
|
||||
<record id="survey_user_input_view_tree" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.tree</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Survey User inputs" decoration-muted="test_entry == True" create="false">
|
||||
@@ -96,8 +98,8 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_survey_user_input_kanban" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.kanban</field>
|
||||
<record id="survey_user_input_viuew_kanban" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.kanban</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban create="false">
|
||||
@@ -135,8 +137,8 @@
|
||||
<field name="name">Answers</field>
|
||||
<field name="res_model">survey.user_input</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="survey_user_input_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_search"/>
|
||||
<field name="view_id" ref="survey_user_input_view_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_view_search"/>
|
||||
<field name="context">{'search_default_group_by_survey': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
@@ -148,8 +150,8 @@
|
||||
<field name="name">Answers</field>
|
||||
<field name="res_model">survey.user_input</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="survey_user_input_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_search"/>
|
||||
<field name="view_id" ref="survey_user_input_view_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_view_search"/>
|
||||
<field name="context">{'search_default_group_by_survey': True}</field>
|
||||
<field name="domain">[('test_entry', '=', False)]</field>
|
||||
<field name="help" type="html">
|
||||
|
||||
Reference in New Issue
Block a user