[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:
Thibault Delavallée
2019-08-29 15:36:32 +00:00
parent 4fbd4a8c6a
commit 0636347099
4 changed files with 204 additions and 172 deletions
+12
View File
@@ -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
+175 -159
View File
@@ -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
+3 -1
View File
@@ -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'),
+14 -12
View File
@@ -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">