[MERGE] survey: reorganize module to prepare future improvements

PURPOSE

Purpose is to perform some cleaning in survey models: naming, ordering,
dead code [0]. As new features are about to land in survey, notably live
interactions [1] and new survey building [2] performing a pre cleaning
is necessary [3].

SPECIFICATIONS

This merge contains three commits doing

  * code move;
  * dead code removal;
  * templates split to ease understanding and reading;

See sub commits for more details.

LINKS

[0] Related to Task ID 2144393 (surveys split / cleaning)
[1] Task ID 1972640 (live interactions)
[2] Task ID 2119587 (new frontend for building surveys)
[3] Task ID 2061901 (survey models cleaning and preparation)

PR #40623

Related: odoo/enterprise#6853
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2019-11-25 14:00:19 +01:00
committed by GitHub
10 changed files with 784 additions and 808 deletions
+2
View File
@@ -35,6 +35,8 @@ sent mails with personal token for the invitation of the survey.
'views/survey_user_views.xml',
'views/survey_question_views.xml',
'views/survey_templates.xml',
'views/survey_templates_management.xml',
'views/survey_templates_statistics.xml',
'views/gamification_badge_views.xml',
'wizard/survey_invite_views.xml',
'views/res_partner_views.xml',
+15 -16
View File
@@ -124,9 +124,9 @@ class Survey(http.Controller):
answer_sudo = access_data['answer_sudo']
if error_key == 'survey_void' and access_data['can_answer']:
return request.render("survey.survey_void", {'survey': survey_sudo, 'answer': answer_sudo})
return request.render("survey.survey_void_content", {'survey': survey_sudo, 'answer': answer_sudo})
elif error_key == 'survey_closed' and access_data['can_answer']:
return request.render("survey.survey_expired", {'survey': survey_sudo})
return request.render("survey.survey_closed_expired", {'survey': survey_sudo})
elif error_key == 'survey_auth' and answer_sudo.token:
if answer_sudo.partner_id and (answer_sudo.partner_id.user_ids or survey_sudo.users_can_signup):
if answer_sudo.partner_id.user_ids:
@@ -136,11 +136,11 @@ class Survey(http.Controller):
redirect_url = answer_sudo.partner_id._get_signup_url_for_action(url='/survey/start/%s?answer_token=%s' % (survey_sudo.access_token, answer_sudo.token))[answer_sudo.partner_id.id]
else:
redirect_url = '/web/login?redirect=%s' % ('/survey/start/%s?answer_token=%s' % (survey_sudo.access_token, answer_sudo.token))
return request.render("survey.auth_required", {'survey': survey_sudo, 'redirect_url': redirect_url})
return request.render("survey.survey_auth_required", {'survey': survey_sudo, 'redirect_url': redirect_url})
elif error_key == 'answer_deadline' and answer_sudo.token:
return request.render("survey.survey_expired", {'survey': survey_sudo})
return request.render("survey.survey_closed_expired", {'survey': survey_sudo})
elif error_key == 'answer_done' and answer_sudo.token:
return request.render("survey.sfinished", self._prepare_survey_finished_values(survey_sudo, answer_sudo, token=answer_sudo.token))
return request.render("survey.survey_closed_finished", self._prepare_survey_finished_values(survey_sudo, answer_sudo, token=answer_sudo.token))
return werkzeug.utils.redirect("/")
@@ -214,12 +214,12 @@ class Survey(http.Controller):
except:
return werkzeug.utils.redirect("/")
else:
return request.render("survey.403", {'survey': survey_sudo})
return request.render("survey.survey_403_page", {'survey': survey_sudo})
# Select the right page
if answer_sudo.state == 'new': # Intro page
data = {'survey': survey_sudo, 'answer': answer_sudo, 'page': 0}
return request.render('survey.survey_init', data)
return request.render('survey.survey_page_start', data)
else:
return request.redirect('/survey/fill/%s/%s' % (survey_sudo.access_token, answer_sudo.token))
@@ -248,7 +248,7 @@ class Survey(http.Controller):
'answer': answer_sudo,
'previous_page_id': new_previous_id
})
return request.render('survey.survey', data)
return request.render('survey.survey_page_main', data)
if survey_sudo.is_time_limited and not answer_sudo.start_datetime:
# init start date when user starts filling in the survey
@@ -266,9 +266,9 @@ class Survey(http.Controller):
})
if is_last:
data.update({'last': True})
return request.render('survey.survey', data)
return request.render('survey.survey_page_main', data)
elif answer_sudo.state == 'done': # Display success message
return request.render('survey.sfinished', self._prepare_survey_finished_values(survey_sudo, answer_sudo))
return request.render('survey.survey_closed_finished', self._prepare_survey_finished_values(survey_sudo, answer_sudo))
elif answer_sudo.state == 'skip':
page_or_question_id, is_last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id)
previous_id = survey_sudo._previous_page_or_question_id(answer_sudo, page_or_question_id.id)
@@ -282,9 +282,9 @@ class Survey(http.Controller):
if is_last:
data.update({'last': True})
return request.render('survey.survey', data)
return request.render('survey.survey_page_main', data)
else:
return request.render("survey.403", {'survey': survey_sudo})
return request.render("survey.survey_403_page", {'survey': survey_sudo})
@http.route('/survey/submit/<string:survey_token>/<string:answer_token>', type='json', auth='public', website=True)
def survey_submit(self, survey_token, answer_token, **post):
@@ -366,9 +366,9 @@ class Survey(http.Controller):
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
if survey_sudo.scoring_type == 'scoring_without_answers':
return request.render("survey.403", {'survey': survey_sudo})
return request.render("survey.survey_403_page", {'survey': survey_sudo})
return request.render('survey.survey_print', {
return request.render('survey.survey_page_print', {
'review': review,
'survey': survey_sudo,
'answer': answer_sudo,
@@ -379,7 +379,6 @@ class Survey(http.Controller):
@http.route('/survey/results/<model("survey.survey"):survey>', type='http', auth='user', website=True)
def survey_report(self, survey, answer_token=None, **post):
'''Display survey Results & Statistics for given survey.'''
result_template = 'survey.result'
current_filters = []
filter_display_data = []
@@ -389,7 +388,7 @@ class Survey(http.Controller):
filter_data = self._get_filter_data(post)
current_filters = survey.filter_input_ids(filter_data, filter_finish)
filter_display_data = survey.get_filter_display_data(filter_data)
return request.render(result_template,
return request.render('survey.survey_page_statistics',
{'survey': survey,
'answers': answers,
'survey_dict': self._prepare_result_dict(survey, current_filters),
+50 -66
View File
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import re
import datetime
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
_logger = logging.getLogger(__name__)
def dict_keys_startswith(dictionary, string):
"""Returns a dictionary containing the elements of <dict> whose keys start with <string>.
.. note::
This function uses dictionary comprehensions (Python >= 2.7)
"""
return {k: v for k, v in dictionary.items() if k.startswith(string)}
class SurveyQuestion(models.Model):
""" Questions that will be asked in a survey.
@@ -50,7 +35,6 @@ class SurveyQuestion(models.Model):
That makes the use and display of these information at view and controller levels easier to understand.
"""
_name = 'survey.question'
_description = 'Survey Question'
_rec_name = 'question'
@@ -63,23 +47,24 @@ class SurveyQuestion(models.Model):
defaults['question_type'] = False if defaults.get('is_page') == True else 'free_text'
return defaults
# Question metadata
# question generic data
title = fields.Char('Title', required=True, translate=True)
question = fields.Char('Question', related="title")
description = fields.Html('Description', help="Use this field to add additional explanations about your question", translate=True)
survey_id = fields.Many2one('survey.survey', string='Survey', ondelete='cascade')
page_id = fields.Many2one('survey.question', string='Page', compute="_compute_page_id", store=True)
question_ids = fields.One2many('survey.question', string='Questions', compute="_compute_question_ids")
scoring_type = fields.Selection(related='survey_id.scoring_type', string='Scoring Type', readonly=True)
sequence = fields.Integer('Sequence', default=10)
# Question
# page specific
is_page = fields.Boolean('Is a page?')
question_ids = fields.One2many('survey.question', string='Questions', compute="_compute_question_ids")
questions_selection = fields.Selection(
related='survey_id.questions_selection', readonly=True,
help="If randomized is selected, add the number of random questions next to the section.")
random_questions_count = fields.Integer(
'Random questions count', default=1,
help="Used on randomized sections to take X random questions from all the questions of that section.")
title = fields.Char('Title', required=True, translate=True)
question = fields.Char('Question', related="title")
description = fields.Html('Description', help="Use this field to add additional explanations about your question", translate=True)
# question specific
page_id = fields.Many2one('survey.question', string='Page', compute="_compute_page_id", store=True)
question_type = fields.Selection([
('free_text', 'Multiple Lines Text Box'),
('textbox', 'Single Line Text Box'),
@@ -89,18 +74,18 @@ class SurveyQuestion(models.Model):
('simple_choice', 'Multiple choice: only one answer'),
('multiple_choice', 'Multiple choice: multiple answers allowed'),
('matrix', 'Matrix')], string='Question Type')
# simple choice / multiple choice / matrix
# -- simple choice / multiple choice / matrix
labels_ids = fields.One2many(
'survey.label', 'question_id', string='Types of answers', copy=True,
help='Labels used for proposed choices: simple choice, multiple choice and columns of matrix')
# matrix
# -- matrix
matrix_subtype = fields.Selection([
('simple', 'One choice per row'),
('multiple', 'Multiple choices per row')], string='Matrix Type', default='simple')
labels_ids_2 = fields.One2many(
'survey.label', 'question_id_2', string='Rows of the Matrix', copy=True,
help='Labels used for proposed choices: rows of matrix')
# Display options
# -- display options
column_nb = fields.Selection([
('12', '1'), ('6', '2'), ('4', '3'), ('3', '4'), ('2', '6')],
string='Number of columns', default='12',
@@ -108,11 +93,11 @@ class SurveyQuestion(models.Model):
display_mode = fields.Selection(
[('columns', 'Radio Buttons'), ('dropdown', 'Selection Box')],
string='Display Mode', default='columns', help='Display mode of simple choice questions.')
# Comments
# -- comments
comments_allowed = fields.Boolean('Show Comments Field')
comments_message = fields.Char('Comment Message', translate=True, default=lambda self: _("If other, please specify:"))
comment_count_as_answer = fields.Boolean('Comment Field is an Answer Choice')
# Validation
# question validation
validation_required = fields.Boolean('Validate entry')
validation_email = fields.Boolean('Input must be an email')
validation_length_min = fields.Integer('Minimum Text Length')
@@ -124,10 +109,9 @@ class SurveyQuestion(models.Model):
validation_min_datetime = fields.Datetime('Minimum Datetime')
validation_max_datetime = fields.Datetime('Maximum Datetime')
validation_error_msg = fields.Char('Validation Error message', translate=True, default=lambda self: _("The answer you entered is not valid."))
# Constraints on number of answers (matrices)
constr_mandatory = fields.Boolean('Mandatory Answer')
constr_error_msg = fields.Char('Error message', translate=True, default=lambda self: _("This question requires an answer."))
# Answer
# answers
user_input_line_ids = fields.One2many(
'survey.user_input_line', 'question_id', string='Answers',
domain=[('skipped', '=', False)], groups='survey.group_survey_user')
@@ -151,6 +135,39 @@ class SurveyQuestion(models.Model):
if self.is_page:
self.question_type = False
@api.depends('survey_id.question_and_page_ids.is_page', 'survey_id.question_and_page_ids.sequence')
def _compute_question_ids(self):
"""Will take all questions of the survey for which the index is higher than the index of this page
and lower than the index of the next page."""
for question in self:
if question.is_page:
next_page_index = False
for page in question.survey_id.page_ids:
if page._index() > question._index():
next_page_index = page._index()
break
question.question_ids = question.survey_id.question_ids.filtered(lambda q:
q._index() > question._index() and (not next_page_index or q._index() < next_page_index))
else:
question.question_ids = self.env['survey.question']
@api.depends('survey_id.question_and_page_ids.is_page', 'survey_id.question_and_page_ids.sequence')
def _compute_page_id(self):
"""Will find the page to which this question belongs to by looking inside the corresponding survey"""
for question in self:
if question.is_page:
question.page_id = None
else:
question.page_id = next(
(iter(question
.survey_id
.question_and_page_ids
.filtered(lambda q: q.is_page and q.sequence < question.sequence)
.sorted(reverse=True))),
None
)
# Validation methods
def validate_question(self, answer, comment=None):
""" Validate question, depending on question type and parameters
@@ -188,7 +205,7 @@ class SurveyQuestion(models.Model):
# Email format validation
# all the strings of the form "<something>@<anything>.<extension>" will be accepted
if self.validation_email:
if not email_validator.match(answer):
if not tools.email_normalize(answer):
return {self.id: _('This answer must be an email address')}
# Answer validation (if properly defined)
@@ -249,39 +266,6 @@ class SurveyQuestion(models.Model):
return {self.id: self.constr_error_msg}
return {}
@api.depends('survey_id.question_and_page_ids.is_page', 'survey_id.question_and_page_ids.sequence')
def _compute_question_ids(self):
"""Will take all questions of the survey for which the index is higher than the index of this page
and lower than the index of the next page."""
for question in self:
if question.is_page:
next_page_index = False
for page in question.survey_id.page_ids:
if page._index() > question._index():
next_page_index = page._index()
break
question.question_ids = question.survey_id.question_ids.filtered(lambda q:
q._index() > question._index() and (not next_page_index or q._index() < next_page_index))
else:
question.question_ids = self.env['survey.question']
@api.depends('survey_id.question_and_page_ids.is_page', 'survey_id.question_and_page_ids.sequence')
def _compute_page_id(self):
"""Will find the page to which this question belongs to by looking inside the corresponding survey"""
for question in self:
if question.is_page:
question.page_id = None
else:
question.page_id = next(
(iter(question
.survey_id
.question_and_page_ids
.filtered(lambda q: q.is_page and q.sequence < question.sequence)
.sorted(reverse=True))),
None
)
def _index(self):
"""We would normally just use the 'sequence' field of questions BUT, if the pages and questions are
created without ever moving records around, the sequence field can be set to 0 for all the questions.
@@ -295,6 +279,7 @@ class SurveyQuestion(models.Model):
return self.labels_ids.filtered(lambda label: label.is_correct)
class SurveyLabel(models.Model):
""" A suggested answer for a question """
_name = 'survey.label'
@@ -307,8 +292,7 @@ class SurveyLabel(models.Model):
sequence = fields.Integer('Label Sequence order', default=10)
value = fields.Char('Suggested value', translate=True, required=True)
is_correct = fields.Boolean('Is a correct answer')
answer_score = fields.Float('Score for this choice',
help="A positive score indicates a correct choice; a negative or null score indicates a wrong answer")
answer_score = fields.Float('Score for this choice', help="A positive score indicates a correct choice; a negative or null score indicates a wrong answer")
@api.constrains('question_id', 'question_id_2')
def _check_question_not_empty(self):
+22 -24
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import random
import uuid
from collections import Counter, OrderedDict
from itertools import product
from werkzeug import urls
import random
from odoo import api, fields, models, _
from odoo.exceptions import UserError
@@ -26,23 +26,24 @@ class Survey(models.Model):
# description
title = fields.Char('Survey Title', required=True, translate=True)
description = fields.Html("Description", translate=True,
description = fields.Html(
"Description", translate=True,
help="The description will be displayed on the home page of the survey. You can use this to give the purpose and guidelines to your candidates before they start it.")
color = fields.Integer('Color Index', default=0)
thank_you_message = fields.Html("Thanks Message", translate=True, help="This message will be displayed when survey is completed")
active = fields.Boolean("Active", default=True)
state = fields.Selection(selection=[
('draft', 'Draft'), ('open', 'In Progress'), ('closed', 'Closed')
], string="Survey Stage", default='draft', required=True,
group_expand='_read_group_states')
category = fields.Selection([
('default', 'Generic Survey')], string='Category',
default='default', required=True,
help='Category is used to know in which context the survey is used. Various apps may define their own categories when they use survey like jobs recruitment or employee appraisal surveys.')
# questions
question_and_page_ids = fields.One2many('survey.question', 'survey_id', string='Sections and Questions', copy=True)
page_ids = fields.One2many('survey.question', string='Pages', compute="_compute_page_and_question_ids")
question_ids = fields.One2many('survey.question', string='Questions', compute="_compute_page_and_question_ids")
state = fields.Selection(
string="Survey Stage",
selection=[
('draft', 'Draft'),
('open', 'In Progress'),
('closed', 'Closed'),
], default='draft', required=True,
group_expand='_read_group_states'
)
questions_layout = fields.Selection([
('one_page', 'One page with all the questions'),
('page_per_section', 'One page per section'),
@@ -53,12 +54,7 @@ class Survey(models.Model):
('random', 'Randomized per section')],
string="Selection", required=True, default='all',
help="If randomized is selected, add the number of random questions next to the section.")
category = fields.Selection([
('default', 'Generic Survey')], string='Category',
default='default', required=True,
help='Category is used to know in which context the survey is used. Various apps may define their own categories when they use survey like jobs recruitment or employee appraisal surveys.')
# content
# attendees
user_input_ids = fields.One2many('survey.user_input', 'survey_id', string='User responses', readonly=True, groups='survey.group_survey_user')
# security / access
access_mode = fields.Selection([
@@ -76,15 +72,14 @@ class Survey(models.Model):
answer_score_avg = fields.Float("Avg Score %", compute="_compute_survey_statistic")
success_count = fields.Integer("Success", compute="_compute_survey_statistic")
success_ratio = fields.Integer("Success Ratio", compute="_compute_survey_statistic")
# scoring and certification fields
# scoring / certification
scoring_type = fields.Selection([
('no_scoring', 'No scoring'),
('scoring_with_answers', 'Scoring with answers at the end'),
('scoring_without_answers', 'Scoring without answers at the end')],
string="Scoring", required=True, default='no_scoring')
passing_score = fields.Float('Passing score (%)', required=True, default=80.0)
is_attempts_limited = fields.Boolean('Limited number of attempts',
help="Check this option if you want to limit the number of attempts per user")
is_attempts_limited = fields.Boolean('Limited number of attempts', help="Check this option if you want to limit the number of attempts per user")
attempts_limit = fields.Integer('Number of attempts', default=1)
is_time_limited = fields.Boolean('The survey is limited in time')
time_limit = fields.Float("Time limit (minutes)")
@@ -93,7 +88,6 @@ class Survey(models.Model):
'mail.template', 'Email Template',
domain="[('model', '=', 'survey.user_input')]",
help="Automated email sent to the user when he succeeds the certification, containing his certification document.")
# Certification badge
# certification_badge_id_dummy is used to have two different behaviours in the form view :
# - If the certification badge is not set, show certification_badge_id and only display create option in the m2o
@@ -218,7 +212,7 @@ class Survey(models.Model):
return super(Survey, self).copy_data(default)
# ------------------------------------------------------------
# TECHNICAL
# ANSWER MANAGEMENT
# ------------------------------------------------------------
def _create_answer(self, user=False, partner=False, email=False, test_entry=False, check_attempts=True, **additional_vals):
@@ -346,8 +340,9 @@ class Survey(models.Model):
return self.attempts_limit - self.env['survey.user_input'].search_count(domain)
# ------------------------------------------------------------
# ACTIONS
# QUESTIONS MANAGEMENT
# ------------------------------------------------------------
@api.model
def _get_pages_or_questions(self, user_input):
if self.questions_layout == 'one_page':
@@ -375,7 +370,6 @@ class Survey(models.Model):
return previous_page_id
@api.model
def next_page_or_question(self, user_input, page_or_question_id):
""" The next page to display to the user, knowing that page_id is the id
@@ -433,6 +427,10 @@ class Survey(models.Model):
questions = questions & answer.question_ids
return questions, page_or_question_id
# ------------------------------------------------------------
# ACTIONS
# ------------------------------------------------------------
def action_draft(self):
self.write({'state': 'draft'})
+58 -73
View File
@@ -3,40 +3,27 @@
import datetime
import logging
import re
import uuid
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
_logger = logging.getLogger(__name__)
def dict_keys_startswith(dictionary, string):
"""Returns a dictionary containing the elements of <dict> whose keys start with <string>.
.. note::
This function uses dictionary comprehensions (Python >= 2.7)
"""
return {k: v for k, v in dictionary.items() if k.startswith(string)}
class SurveyUserInput(models.Model):
""" Metadata for a set of one user's answers to a particular survey """
_name = "survey.user_input"
_rec_name = 'survey_id'
_description = 'Survey User Input'
# description
# answer description
survey_id = fields.Many2one('survey.survey', string='Survey', required=True, readonly=True, ondelete='cascade')
scoring_type = fields.Selection(string="Scoring", related="survey_id.scoring_type")
is_attempts_limited = fields.Boolean("Limited number of attempts", related='survey_id.is_attempts_limited')
attempts_limit = fields.Integer("Number of attempts", related='survey_id.attempts_limit')
start_datetime = fields.Datetime('Start date and time', readonly=True)
is_time_limit_reached = fields.Boolean("Is time limit reached?", compute='_compute_is_time_limit_reached')
deadline = fields.Datetime('Deadline', help="Datetime until customer can open the survey and submit answers")
input_type = fields.Selection([
('manually', 'Manual'), ('link', 'Invitation')],
string='Answer Type', default='manually', required=True, readonly=True)
@@ -45,24 +32,26 @@ class SurveyUserInput(models.Model):
('skip', 'Partially completed'),
('done', 'Completed')], string='Status', default='new', readonly=True)
test_entry = fields.Boolean(readonly=True)
# identification and access
last_displayed_page_id = fields.Many2one('survey.question', string='Last displayed question/page')
# attempts management
is_attempts_limited = fields.Boolean("Limited number of attempts", related='survey_id.is_attempts_limited')
attempts_limit = fields.Integer("Number of attempts", related='survey_id.attempts_limit')
attempt_number = fields.Integer("Attempt n°", compute='_compute_attempt_number')
is_time_limit_reached = fields.Boolean("Is time limit reached?", compute='_compute_is_time_limit_reached')
# identification / 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)
invite_token = fields.Char('Invite token', readonly=True, copy=False) # no unique constraint, as it identifies a pool of attempts
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
email = fields.Char('E-mail', readonly=True)
attempt_number = fields.Integer("Attempt n°", compute='_compute_attempt_number')
# Displaying data
last_displayed_page_id = fields.Many2one('survey.question', string='Last displayed question/page')
# answers
# questions / answers
user_input_line_ids = fields.One2many('survey.user_input_line', 'user_input_id', string='Answers', copy=True)
# Pre-defined questions
question_ids = fields.Many2many('survey.question', string='Predefined Questions', readonly=True)
deadline = fields.Datetime('Deadline', help="Datetime until customer can open the survey and submit answers")
# Stored for performance reasons while displaying results page
quizz_score = fields.Float("Score (%)", compute="_compute_quizz_score", store=True, compute_sudo=True)
quizz_passed = fields.Boolean('Quizz Passed', compute='_compute_quizz_passed', store=True, compute_sudo=True)
quizz_score = fields.Float("Score (%)", compute="_compute_quizz_score", store=True, compute_sudo=True) # stored for perf reasons
quizz_passed = fields.Boolean('Quizz Passed', compute='_compute_quizz_passed', store=True, compute_sudo=True) # stored for perf reasons
_sql_constraints = [
('unique_token', 'UNIQUE (token)', 'A token must be unique!'),
]
@api.depends('user_input_line_ids.answer_score', 'user_input_line_ids.question_id')
def _compute_quizz_score(self):
@@ -83,49 +72,6 @@ class SurveyUserInput(models.Model):
for user_input in self:
user_input.quizz_passed = user_input.quizz_score >= user_input.survey_id.passing_score
_sql_constraints = [
('unique_token', 'UNIQUE (token)', 'A token must be unique!'),
]
@api.model
def do_clean_emptys(self):
""" Remove empty user inputs that have been created manually
(used as a cronjob declared in data/survey_cron.xml)
"""
an_hour_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(hours=1))
self.search([('input_type', '=', 'manually'),
('state', '=', 'new'),
('create_date', '<', an_hour_ago)]).unlink()
@api.model
def _generate_invite_token(self):
return str(uuid.uuid4())
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()
def action_print_answers(self):
""" Open the website page with the survey form """
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'name': "View Answers",
'target': 'self',
'url': '/survey/print/%s?answer_token=%s' % (self.survey_id.access_token, self.token)
}
@api.depends('start_datetime', 'survey_id.is_time_limited', 'survey_id.time_limit')
def _compute_is_time_limit_reached(self):
""" Checks that the user_input is not exceeding the survey's time limit. """
@@ -167,6 +113,45 @@ class SurveyUserInput(models.Model):
user_input.attempt_number = attempt_number
@api.model
def do_clean_emptys(self):
""" Remove empty user inputs that have been created manually
(used as a cronjob declared in data/survey_cron.xml)
"""
an_hour_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(hours=1))
self.search([('input_type', '=', 'manually'),
('state', '=', 'new'),
('create_date', '<', an_hour_ago)]).unlink()
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()
def action_print_answers(self):
""" Open the website page with the survey form """
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'name': "View Answers",
'target': 'self',
'url': '/survey/print/%s?answer_token=%s' % (self.survey_id.access_token, self.token)
}
@api.model
def _generate_invite_token(self):
return str(uuid.uuid4())
def _mark_done(self):
""" This method will:
1. mark the state as 'done'
+112 -626
View File
@@ -1,154 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Forbidden error messages-->
<template id="403">
<t t-call="survey.layout">
<div id="wrap">
<div class="container">
<h1 class="mt32">403: Forbidden</h1>
<p>The page you were looking for could not be authorized.</p>
<p>Maybe you were looking for
<a t-attf-href="/web#view_type=form&amp;model=survey.survey&amp;id=#{survey.id}&amp;action=survey.action_survey_form">this page</a> ?
</p>
</div>
</div>
</t>
</template>
<!-- "Thank you" message when the survey is completed -->
<template id="sfinished" name="Survey Finished">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<t t-if="answer.test_entry" t-call="survey.back" />
<div class="jumbotron mt32">
<h1>Thank you!</h1>
<div t-field="survey.thank_you_message" class="oe_no_empty" />
<div class="row">
<div class="col">
<t t-if="survey.scoring_type != 'no_scoring'">
<div>You scored <t t-esc="answer.quizz_score" />%</div>
<t t-if="answer.quizz_passed">
<div>Congratulations, you have passed the test!</div>
<div t-if="survey.certificate" class="mt16 mb16">
<a role="button"
class="btn btn-primary btn-lg"
t-att-href="'/survey/%s/get_certification' % survey.id">
<i class="fa fa-fw fa-trophy" role="img" aria-label="Download certification" title="Download certification"/>
Download certification
</a>
</div>
</t>
<t t-else="">
<div>Unfortunately, you have failed the test.</div>
</t>
</t>
<t t-call="survey.retake_survey_button"/>
<div t-if="survey.scoring_type != 'scoring_without_answers'">
If you wish, you can <a t-att-href="'/survey/print/%s?answer_token=%s&amp;review=True' % (survey.access_token, answer.token)">review your answers</a>
</div>
</div>
<div class="col-6 text-center" t-if="survey.certification_give_badge and answer.quizz_passed">
<img t-att-src="'/web/image/gamification.badge/%s/image_128' % survey.certification_badge_id.id"/>
<div>You received the badge <span class="font-weight-bold" t-esc="survey.certification_badge_id.name"/>!</div>
</div>
</div>
</div>
</div>
<div class="container o_survey_result p-4" t-if="graph_data">
<div class="tab-content">
<div role="tabpanel" class="tab-pane active survey_graph" t-att-data-quizz_score="answer.quizz_score" t-att-id="'graph_question_%d' % answer.id" t-att-data-question_id="answer.id" data-graph-type="doughnut" t-att-data-graph-data="graph_data">
<canvas id="doughnut_chart"></canvas>
<span class="o_overall_performance"></span>
</div>
</div>
</div>
<div class="oe_structure"/>
</div>
</t>
</template>
<template id="retake_survey_button" name="Retake Survey Button">
<div>
<t t-if="not answer.quizz_passed">
<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, answer.invite_token)" />
<t t-if="attempts_left > 0">
<p><span>Number of attemps left</span>: <span t-esc="attempts_left"></span></p>
<p><a role="button" class="btn btn-primary btn-lg" t-att-href="'/survey/retry/%s/%s' % (survey.access_token, answer.token)">
Retry</a></p>
</t>
</t>
<t t-else="">
<p><a role="button" class="btn btn-primary btn-lg" t-att-href="'/survey/retry/%s/%s' % (survey.access_token, answer.token)">
Retry</a></p>
</t>
</t>
</div>
</template>
<!-- Message when the survey is not open -->
<template id="survey_expired" name="Survey Expired">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<div class="jumbotron mt32">
<h1><span t-field="survey.title"/> survey expired</h1>
<p>This survey is now closed. Thank you for your interest !</p>
</div>
</div>
</div>
</t>
</template>
<!-- Message when a login is required -->
<template id="auth_required" name="Login required for this survey">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<div class="jumbotron mt32">
<h1>Login required</h1>
<p>This survey is open only to registered people. Please
<a t-att-href="redirect_url">log in</a>.
</p>
</div>
</div>
</div>
</t>
</template>
<!-- Message when the survey has no pages -->
<template id="survey_void" name="Survey has no pages">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<t t-if="answer.test_entry" t-call="survey.back" />
<div class="jumbotron mt32">
<h1><span t-field="survey.title"/> survey is void</h1>
<p>Please make sure you have at least one question in your survey. You also need at least one section if you chose the "Page per section" layout.<br />
<a t-att-href="'/web#view_type=form&amp;model=survey.survey&amp;id=%s&amp;action=survey.action_survey_form' % survey.id"
class="btn btn-secondary"
groups="survey.group_survey_manager">Edit in backend</a>
</p>
</div>
</div>
</div>
</t>
</template>
<!-- Back Button to redirect in form view of survey -->
<template id="back" name="Back">
<div groups="survey.group_survey_manager" t-ignore="true" class="alert alert-info alert-dismissible rounded-0 mt16 fade show d-print-none css_editable_mode_hidden">
<div t-ignore="true" class="text-center">
<a t-attf-href="/web#view_type=form&amp;model=survey.survey&amp;id=#{survey.id}&amp;action=survey.action_survey_form"><i class="fa fa-fw fa-arrow-right"/><span t-if="answer and answer.test_entry">This is a test survey. </span>Edit Survey.</a>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> &#215; </button>
</div>
</template>
<!-- new layout for survey -->
<!-- Main survey layout -->
<template id="survey.layout" name="Survey Layout" inherit_id="web.frontend_layout" primary="True">
<xpath expr="//head/t[@t-call-assets][last()]" position="after">
<t t-call-assets="survey.survey_assets" lazy_load="True"/>
@@ -163,12 +16,12 @@
</template>
<!-- First page of a survey -->
<template id="survey_init" name="Survey">
<template id="survey_page_start" name="Survey: start page">
<t t-call="survey.layout">
<div class="wrap">
<div class="oe_structure" id="oe_structure_survey_init_1"/>
<div class="container">
<t t-if="answer.test_entry" t-call="survey.back" />
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<div class='jumbotron mt32'>
<h1 t-field='survey.title' />
<div t-field='survey.description' class="oe_no_empty"/>
@@ -193,23 +46,95 @@
</t>
</template>
<!-- A survey -->
<template id="survey" name="Survey">
<template id="survey_page_main" name="Survey: main page (take survey)">
<t t-call="survey.layout">
<div class="wrap">
<div class="oe_structure" id="oe_structure_survey_survey_1"/>
<div class="container o_survey_form">
<t t-if="answer.test_entry" t-call="survey.back" />
<t t-call="survey.survey_header" />
<t t-call="survey.page" />
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<t t-call="survey.survey_page_main_header" />
<t t-call="survey.survey_page_content" />
</div>
<div class="oe_structure" id="oe_structure_survey_survey_2"/>
</div>
</t>
</template>
<!-- The survey header -->
<template id="survey_header" name="Header">
<template id="survey_page_content" name="Survey: main page content">
<t t-set="survey_form_readonly" t-value="false"/>
<t t-if="survey.questions_layout == 'one_page'" t-set="page_or_question_id" t-value="None" />
<t t-if="survey.questions_layout == 'page_per_section'" t-set="page_or_question_id" t-value="page.id" />
<t t-if="survey.questions_layout == 'page_per_question'" t-set="page_or_question_id" t-value="question.id" />
<form role="form" method="post" t-att-name="survey.id"
t-att-data-answer-token="answer.token"
t-att-data-survey-token="survey.access_token"
t-att-data-timer="answer.start_datetime.isoformat() if survey.is_time_limited else False"
t-att-data-time-limit-minutes="survey.time_limit">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="token" t-att-value="answer.token" />
<div class="o_survey_error alert alert-danger d-none" role="alert">
<p>Their was an error during the validation of the survey.</p>
</div>
<t t-if="survey.questions_layout == 'one_page'">
<t t-foreach='survey.question_and_page_ids' t-as='question'>
<h2 t-if="question.is_page" t-field='question.title' class="o_survey_title" />
<t t-if="not question.is_page and question in answer.question_ids" t-call="survey.question_container"/>
</t>
<div class="text-center mt16 mb256">
<button type="submit" class="btn btn-primary">Submit Survey</button>
</div>
</t>
<t t-if="survey.questions_layout == 'page_per_section'">
<h2 t-field='page.title' class="o_survey_title" />
<div t-field='page.description' class="oe_no_empty"/>
<input type="hidden" name="page_id" t-att-value="page.id" />
<t t-foreach='page.question_ids' t-as='question'>
<t t-if="question in answer.question_ids" t-call="survey.question_container"/>
</t>
<div class="text-center mt16 mb256">
<button t-if="survey.users_can_go_back and page != survey.page_ids[0]" type="submit" class="btn btn-secondary"
name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Previous page</button>
<button type="submit" t-att-value="'next' if not last else 'finish'" class="btn btn-primary">
<t t-esc="'Next Page' if not last else 'Submit Survey'"/>
</button>
</div>
</t>
<t t-if="survey.questions_layout == 'page_per_question'">
<input type="hidden" name="question_id" t-att-value="question.id" />
<t t-call="survey.question_container"/>
<div class="text-center mt16 mb256">
<button t-if="survey.users_can_go_back and question != answer.question_ids[0]" type="submit" class="btn btn-secondary"
name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Previous page</button>
<button type="submit" t-att-value="'next' if not last else 'finish'" class="btn btn-primary">
<t t-esc="'Next Page' if not last else 'Submit Survey'"/>
</button>
</div>
</t>
</form>
<!-- Modal used to display error message, i.c.o. ajax error -->
<div role="dialog" class="modal fade" id="AJAXErrorModal" >
<div class="modal-dialog">
<div class="modal-content">
<header class="modal-header">
<h4 class="modal-title">A problem has occured</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&amp;times;</button>
</header>
<main class="modal-body"><p>Something went wrong while contacting survey server. <strong class="text-danger">Your answers have probably not been recorded.</strong> Try refreshing.</p></main>
<footer class="modal-footer"><button type="button" class="btn btn-primary" data-dismiss="modal">Close</button></footer>
</div>
</div>
</div>
</template>
<template id="survey_page_main_header" name="Survey: main page header">
<div class="o_page_header o_survey_header">
<div class="container m-0 p-0">
<div class="row">
@@ -242,83 +167,8 @@
</div>
</template>
<!-- A page -->
<template id="page" name="Page">
<t t-set="survey_form_readonly" t-value="false"/>
<t t-if="survey.questions_layout == 'one_page'" t-set="page_or_question_id" t-value="None" />
<t t-if="survey.questions_layout == 'page_per_section'" t-set="page_or_question_id" t-value="page.id" />
<t t-if="survey.questions_layout == 'page_per_question'" t-set="page_or_question_id" t-value="question.id" />
<form role="form" method="post" t-att-name="survey.id"
t-att-data-answer-token="answer.token"
t-att-data-survey-token="survey.access_token"
t-att-data-timer="answer.start_datetime.isoformat() if survey.is_time_limited else False"
t-att-data-time-limit-minutes="survey.time_limit">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="token" t-att-value="answer.token" />
<div class="o_survey_error alert alert-danger d-none" role="alert">
<p>Their was an error during the validation of the survey.</p>
</div>
<t t-if="survey.questions_layout == 'one_page'">
<t t-foreach='survey.question_and_page_ids' t-as='question'>
<h2 t-if="question.is_page" t-field='question.title' class="o_survey_title" />
<t t-if="not question.is_page and question in answer.question_ids" t-call="survey.question" />
</t>
<div class="text-center mt16 mb256">
<button type="submit" class="btn btn-primary">Submit Survey</button>
</div>
</t>
<t t-if="survey.questions_layout == 'page_per_section'">
<h2 t-field='page.title' class="o_survey_title" />
<div t-field='page.description' class="oe_no_empty"/>
<input type="hidden" name="page_id" t-att-value="page.id" />
<t t-foreach='page.question_ids' t-as='question'>
<t t-if="question in answer.question_ids" t-call="survey.question" />
</t>
<div class="text-center mt16 mb256">
<button t-if="survey.users_can_go_back and page != survey.page_ids[0]" type="submit" class="btn btn-secondary"
name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Previous page</button>
<button type="submit" t-att-value="'next' if not last else 'finish'" class="btn btn-primary">
<t t-esc="'Next Page' if not last else 'Submit Survey'"/>
</button>
</div>
</t>
<t t-if="survey.questions_layout == 'page_per_question'">
<input type="hidden" name="question_id" t-att-value="question.id" />
<t t-call="survey.question" />
<div class="text-center mt16 mb256">
<button t-if="survey.users_can_go_back and question != answer.question_ids[0]" type="submit" class="btn btn-secondary"
name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Previous page</button>
<button type="submit" t-att-value="'next' if not last else 'finish'" class="btn btn-primary">
<t t-esc="'Next Page' if not last else 'Submit Survey'"/>
</button>
</div>
</t>
</form>
<!-- Modal used to display error message, i.c.o. ajax error -->
<div role="dialog" class="modal fade" id="AJAXErrorModal" >
<div class="modal-dialog">
<div class="modal-content">
<header class="modal-header">
<h4 class="modal-title">A problem has occured</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&amp;times;</button>
</header>
<main class="modal-body"><p>Something went wrong while contacting survey server. <strong class="text-danger">Your answers have probably not been recorded.</strong> Try refreshing.</p></main>
<footer class="modal-footer"><button type="button" class="btn btn-primary" data-dismiss="modal">Close</button></footer>
</div>
</div>
</div>
</template>
<template id="question" name="Question">
<!-- Question widgets -->
<template id="question_container" name="Survey: question container">
<t t-set="answer_lines" t-value="answer.user_input_line_ids.filtered(lambda line: line.question_id == question)"/>
<div class="js_question-wrapper" t-att-id="question.id">
<h4>
@@ -326,35 +176,34 @@
<span t-if="question.constr_mandatory" class="text-danger">*</span>
</h4>
<div t-field='question.description' class="text-muted oe_no_empty"/>
<t t-if="question.question_type == 'free_text'"><t t-call="survey.free_text"/></t>
<t t-if="question.question_type == 'textbox'"><t t-call="survey.textbox"/></t>
<t t-if="question.question_type == 'numerical_box'"><t t-call="survey.numerical_box"/></t>
<t t-if="question.question_type == 'date'"><t t-call="survey.date"/></t>
<t t-if="question.question_type == 'datetime'"><t t-call="survey.datetime"/></t>
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.simple_choice"/></t>
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.multiple_choice"/></t>
<t t-if="question.question_type == 'matrix'"><t t-call="survey.matrix"/></t>
<t t-if="question.question_type == 'free_text'"><t t-call="survey.question_free_text"/></t>
<t t-if="question.question_type == 'textbox'"><t t-call="survey.question_textbox"/></t>
<t t-if="question.question_type == 'numerical_box'"><t t-call="survey.question_numerical_box"/></t>
<t t-if="question.question_type == 'date'"><t t-call="survey.question_date"/></t>
<t t-if="question.question_type == 'datetime'"><t t-call="survey.question_datetime"/></t>
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.question_simple_choice"/></t>
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.question_multiple_choice"/></t>
<t t-if="question.question_type == 'matrix'"><t t-call="survey.question_matrix"/></t>
<div class="o_survey_question_error alert alert-danger d-none" role="alert"></div>
</div>
</template>
<!-- Question widgets -->
<template id="free_text" name="Free text box">
<template id="question_free_text" name="Question: free text box">
<textarea class="form-control o_survey_question_free_text" rows="3" t-att-name="question.id"
t-att-data-question-type="question.question_type"><t t-if="answer_lines" t-esc="answer_lines[0].value_free_text or None"/></textarea>
</template>
<template id="textbox" name="Text box">
<template id="question_textbox" name="Question: text box">
<input type="text" class="form-control o_survey_question_text_box" t-att-name="question.id" t-att-value="answer_lines[0].value_text if answer_lines else None"
t-att-data-question-type="question.question_type"/>
</template>
<template id="numerical_box" name="Numerical box">
<template id="question_numerical_box" name="Question: numerical box">
<input type="number" step="any" class="form-control o_survey_question_numerical_box" t-att-name="question.id" t-att-value="answer_lines[0].value_number if answer_lines else None"
t-att-data-question-type="question.question_type"/>
</template>
<template id="date" name="Date box">
<template id="question_date" name="Question: date box">
<div class="input-group o_survey_form_date" t-attf-id="datetimepicker_#{question.id}" data-target-input="nearest"
t-att-data-mindate="question.validation_min_date"
t-att-data-maxdate="question.validation_max_date">
@@ -368,7 +217,7 @@
</div>
</template>
<template id="datetime" name="DateTime box">
<template id="question_datetime" name="Question: datetime box">
<div class="input-group o_survey_form_date" t-attf-id="datetimepicker_#{question.id}" data-target-input="nearest"
t-att-data-mindate="question.validation_min_datetime"
t-att-data-maxdate="question.validation_max_datetime">
@@ -382,7 +231,7 @@
</div>
</template>
<template id="simple_choice" name="Simple choice">
<template id="question_simple_choice" name="Question: simple choice">
<t t-set="answer_line" t-value="answer_lines.filtered(lambda line: line.value_suggested)"/>
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_text)"/>
<div t-if="question.display_mode == 'dropdown'" class="o_survey_form_choice o_survey_question_simple_choice_dropdown row"
@@ -439,7 +288,7 @@
</div>
</template>
<template id="multiple_choice" name="Multiple choice">
<template id="question_multiple_choice" name="Question: multiple choice">
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_text)"/>
<div class="row o_survey_form_choice o_survey_question_multiple_choice"
t-att-data-name="question.id"
@@ -469,7 +318,7 @@
</div>
</template>
<template id="matrix" name="Matrix">
<template id="question_matrix" name="Question: matrix">
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_text)"/>
<table class="table table-hover o_survey_question_matrix"
t-att-data-name="question.id"
@@ -499,17 +348,17 @@
</div>
</template>
<!-- Printable view of a survey (all pages) -->
<template id="survey_print" name="Survey">
<!-- Survey: printable page view (all pages) -->
<template id="survey_page_print" name="Survey: print page">
<t t-call="survey.layout">
<t t-set="survey_form_readonly" t-value="true"/>
<div class="wrap">
<div class="container">
<t t-if="answer.test_entry" t-call="survey.back" />
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<div class='jumbotron mt32'>
<h1><span t-field='survey.title'/></h1>
<t t-if="survey.description"><div t-field='survey.description' class="oe_no_empty"/></t>
<t t-if="review" t-call="survey.retake_survey_button"/>
<t t-if="review" t-call="survey.survey_button_retake"/>
</div>
<div role="form">
<fieldset disabled="disabled">
@@ -528,14 +377,14 @@
<span t-if="quizz_correction" class="badge badge-pill" t-att-data-score-question="question.id"></span>
</h2>
<t t-if="question.description"><div class="text-muted oe_no_empty" t-field='question.description'/></t>
<t t-if="question.question_type == 'free_text'"><t t-call="survey.free_text"/></t>
<t t-if="question.question_type == 'textbox'"><t t-call="survey.textbox"/></t>
<t t-if="question.question_type == 'numerical_box'"><t t-call="survey.numerical_box"/></t>
<t t-if="question.question_type == 'date'"><t t-call="survey.date"/></t>
<t t-if="question.question_type == 'datetime'"><t t-call="survey.datetime"/></t>
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.simple_choice"/></t>
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.multiple_choice"/></t>
<t t-if="question.question_type == 'matrix'"><t t-call="survey.matrix"/></t>
<t t-if="question.question_type == 'free_text'"><t t-call="survey.question_free_text"/></t>
<t t-if="question.question_type == 'textbox'"><t t-call="survey.question_textbox"/></t>
<t t-if="question.question_type == 'numerical_box'"><t t-call="survey.question_numerical_box"/></t>
<t t-if="question.question_type == 'date'"><t t-call="survey.question_date"/></t>
<t t-if="question.question_type == 'datetime'"><t t-call="survey.question_datetime"/></t>
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.question_simple_choice"/></t>
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.question_multiple_choice"/></t>
<t t-if="question.question_type == 'matrix'"><t t-call="survey.question_matrix"/></t>
<div class="o_survey_question_error alert alert-danger d-none" role="alert"></div>
</div>
</t>
@@ -546,368 +395,5 @@
</div>
</t>
</template>
<template id="result" name="Survey Result">
<t t-call="survey.layout">
<div class="oe_structure" id="oe_structure_survey_result_1"/>
<div class="container o_survey_result">
<t t-call="survey.back" />
<div class="jumbotron mt32">
<h1><span t-field="survey.title" />
<span style="font-size:1.5em;" t-attf-class="fa fa-bar-chart-o #{'fa-bar-chart-o' if survey.scoring_type == 'no_scoring' else 'fa-trophy' if survey.certificate else 'fa-question-circle-o'} float-right " role="img" aria-label="Chart" title="Chart"/></h1>
<div t-field="survey.description" class="oe_no_empty" />
<h2 t-if="not answers">
Sorry, no one answered this survey yet.
</h2>
</div>
<div t-if="answers" class="card d-print-none">
<div class="card-header"><span class="fa fa-filter"></span> Filters <span t-if="filter_display_data" class="float-right text-primary clear_survey_filter"><i class="fa fa-times"></i> Clear All Filters</span></div>
<div class="card-body">
<span t-if="filter_finish == True">
<span class="badge badge-secondary only_left_radius filter-all">All surveys</span><span class="badge badge-primary only_right_radius filter-finished o_active_filter">Finished surveys</span>
</span>
<span t-if="filter_finish == False">
<span class="badge badge-primary only_left_radius filter-all o_active_filter">All surveys</span><span class="badge badge-secondary only_right_radius filter-finished">Finished surveys</span>
</span>
<span t-foreach="filter_display_data" t-as="filter_data">
<span class="badge badge-primary only_left_radius"><i class="fa fa-filter" role="img" aria-label="Filter" title="Filter"></i></span><span class="badge badge-primary no_radius" t-esc="filter_data['question_text']"></span><span class="badge badge-success only_right_radius" t-esc="' > '.join(filter_data['labels'])"></span>
</span>
</div>
</div>
<div t-if="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers']">
<h1 class="mt16">Results Overview</h1>
<div>Success rate: <mark class="font-weight-bold"><t t-esc="survey_dict['success_rate']"></t>%</mark></div>
<div class="survey_graph" data-graph-type="pie" t-att-data-graph-data="survey_dict['scoring_graph_data']">
<!-- canvas element for drawing pie chart -->
<canvas/>
</div>
<hr/>
</div>
<div t-if="answers" t-foreach="survey_dict['page_ids']" t-as='page_ids'>
<t t-set="page" t-value="page_ids['page']"/>
<h1 class="mt16" t-field='page.title'></h1>
<div t-field="page.description" class="oe_no_empty" />
<hr/>
<div t-foreach="page_ids['question_ids']" t-as='question_ids' class="mt16">
<t t-set="input_summary" t-value="question_ids['input_summary']"/>
<t t-set="question" t-value="question_ids['question']"/>
<t t-set="graph_data" t-value="question_ids['graph_data']"/>
<t t-set="prepare_result" t-value="question_ids['prepare_result']"/>
<t t-set="question_with_correct_answers" t-value="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers'] and question.question_type in ['simple_choice', 'multiple_choice']" />
<h4>
<b>Question </b>
<span t-field='question.question'></span>
<t t-if="question.question_type == 'matrix'">
<small><span class="badge badge-secondary">Matrix: <span t-field='question.matrix_subtype'/></span></small>
</t>
<t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
<small><span t-field='question.question_type' class="badge badge-secondary"></span></small>
</t>
<span class="float-right">
<span class="badge badge-success"><span t-esc="input_summary['answered']"></span> Answered</span>
<span class="badge badge-warning"><span t-esc="input_summary['skipped']"></span> Skipped</span>
</span>
</h4>
<t t-if="question_with_correct_answers">
<t t-set="correct_answers" t-value="question.get_correct_answer_ids()" />
<div t-if="correct_answers">
<t t-if="question.question_type == 'simple_choice'">
<p><span>Correct answer</span>: <span class="font-weight-bold" t-esc="correct_answers[0].value"></span></p>
</t>
<t t-if="question.question_type == 'multiple_choice'">
<span>Correct answers</span>:
<ul>
<li t-foreach="correct_answers" t-as="correct_answer">
<t t-esc="correct_answer.value" />
</li>
</ul>
</t>
</div>
</t>
<t t-if="input_summary['answered'] != 0">
<t t-if="question.description">
<div class="text-muted oe_no_empty" t-field="question.description" />
</t>
<t t-if="question.question_type in ['textbox', 'free_text', 'date', 'datetime']">
<t t-call="survey.result_text"></t>
</t>
<t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
<t t-call="survey.result_choice"></t>
</t>
<t t-if="question.question_type == 'matrix'">
<t t-call="survey.result_matrix"></t>
</t>
<t t-if="question.question_type == 'numerical_box'">
<t t-call="survey.result_number"></t>
</t>
</t>
<t t-if="input_summary['answered'] == 0">
<h2 style="padding-top:30px;padding-bottom:30px;text-align:center;" class="text-muted">Sorry, No one answered this question.</h2>
</t>
</div>
</div>
</div>
<div class="oe_structure" id="oe_structure_survey_result_2"/>
</t>
</template>
<!-- Result for free_text,textbox and date -->
<template id="result_text" name="Text Result">
<table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
<th>User Responses</th>
</tr>
</thead>
<tbody>
<t t-set="text_result" t-value="prepare_result"/>
<tr t-foreach="text_result" t-as="user_input">
<td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
<t t-if="question.question_type == 'free_text'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_free_text"></a><br/>
</td>
</t>
<t t-if="question.question_type == 'textbox'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_text"></a><br/>
</td>
</t>
<t t-if="question.question_type == 'date'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_date"></a><br/>
</td>
</t>
<t t-if="question.question_type == 'datetime'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_datetime"></a><br/>
</td>
</t>
</tr>
</tbody>
</table>
<t t-call="survey.pagination" />
</template>
<!-- Result for comments -->
<template id="result_comments" name="Text Result">
<!-- a 'comments' variable must be set an must contain a list of browse records of user input lines -->
<table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<tr t-foreach="comments" t-as="user_input">
<td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
<td>
<span t-field="user_input.value_text"></span><br/>
</td>
</tr>
</tbody>
</table>
</template>
<!-- Result for simple_choice and multiple_choice -->
<template id="result_choice" name="Choice Result">
<div>
<!-- Tabs -->
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item" t-if="question.question_type != 'simple_choice'">
<a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-bar-chart-o"></i> Graph
</a>
</li>
<li class="nav-item" t-if="question.question_type == 'simple_choice'">
<a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-bar-chart-o"></i> Pie Chart
</a>
</li>
<li class="nav-item">
<a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
<i class="fa fa-list-alt"></i> Data
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active survey_graph" t-if="question.question_type != 'simple_choice'" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id="question.id" data-graph-type="bar" t-att-data-graph-data="graph_data">
<!-- canvas element for drawing bar chart -->
<canvas/>
</div>
<div role="tabpanel" class="tab-pane active survey_graph" t-if="question.question_type == 'simple_choice'" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id="question.id" data-graph-type="pie" t-att-data-graph-data="graph_data">
<!-- canvas element for drawing pie chart -->
<canvas/>
</div>
<div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
<table class="table table-hover table-sm">
<thead>
<tr>
<th>Answer Choices</th>
<th>User Responses</th>
<th t-if="question_with_correct_answers">Answer Score</th>
</tr>
</thead>
<tbody>
<tr t-foreach="prepare_result['answers']" t-as="user_input">
<td>
<p t-esc="user_input['text']"></p>
</td>
<td class="survey_answer">
<span t-esc="round(user_input['count']*100.0/(input_summary['answered'] or 1),2)"></span> %
<span t-esc="user_input['count']" class="badge badge-primary">Vote</span>
<i class="fa fa-filter text-primary survey_filter" t-att-data-question_id="question.id" t-att-data-answer_id="user_input['answer_id']" role="img" aria-label="Filter question" title="Filter question"/>
</td>
<td t-if="question_with_correct_answers" t-esc="user_input['answer_score']">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- handle comments -->
<div>
<t t-set="comments" t-value="prepare_result['comments']" />
<t t-if="comments">
<t t-call="survey.result_comments" />
</t>
</div>
</div>
</template>
<!-- Result for matrix -->
<template id="result_matrix" name="Matrix Result">
<t t-set="matrix_result" t-value="prepare_result"/>
<!-- Tabs -->
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item">
<a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-bar-chart"></i>
Graph
</a>
</li>
<li class="nav-item">
<a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
<i class="fa fa-list-alt"></i>
Data
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions survey_graph" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id= "question.id" data-graph-type= "multi_bar" t-att-data-graph-data="graph_data">
<!-- canvas element for drawing Multibar chart -->
<canvas/>
</div>
<div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
<table class="table table-hover table-sm text-right">
<thead>
<tr>
<th></th>
<th class="text-right" t-foreach="matrix_result['answers']" t-as="answer_id">
<span t-esc="matrix_result['answers'][answer_id]"></span>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="matrix_result['rows']" t-as="row_id">
<td>
<span t-esc="matrix_result['rows'][row_id]"></span>
</td>
<td class="survey_answer" t-foreach="matrix_result['answers']" t-as="answer_id">
<span t-esc="round(matrix_result['result'][(row_id,answer_id)]*100.0/(input_summary['answered'] or 1),2)"></span> %
<span class="badge badge-primary" t-esc="matrix_result['result'][(row_id,answer_id)]"></span><i class="fa fa-filter text-primary survey_filter" t-att-data-question_id="question.id" t-att-data-row_id="row_id" t-att-data-answer_id="answer_id" role="img" aria-label="Survey filter" title="Survey filter"></i>
</td>
</tr>
</tbody>
</table>
</div>
<!-- handle comments to matrix -->
<div>
<t t-set="comments" t-value="matrix_result['comments']" />
<t t-if="comments">
<t t-call="survey.result_comments" />
</t>
</div>
</div>
</template>
<!-- Result for Numeric Data -->
<template id="result_number" name="Number Result">
<t t-set="number_result" t-value="prepare_result"/>
<t t-set="text_result" t-value="number_result['input_lines']" />
<span class="float-right mt8">
<span class="badge badge-secondary only_left_radius">Sum </span> <span class="badge badge-info only_right_radius" t-esc="number_result['sum']"></span>
<span class="badge badge-secondary only_left_radius">Maximum </span> <span class="badge badge-success only_right_radius" t-esc="number_result['max']"></span>
<span class="badge badge-secondary only_left_radius">Minimum </span> <span class="badge badge-danger only_right_radius" t-esc="number_result['min']"></span>
<span class="badge badge-secondary only_left_radius">Average </span> <span class="badge badge-warning only_right_radius" t-esc="number_result['average']"></span>
</span>
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item">
<a t-att-href="'#most_common_%d' % question.id" t-att-aria-controls="'most_common_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-list-ol"></i>
Most Common <span t-esc="len(number_result['most_common'])"></span>
</a>
</li>
<li class="nav-item">
<a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
<i class="fa fa-list-alt"></i>
All Data
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions" t-att-id="'most_common_%d' % question.id">
<table class="table table-hover table-sm">
<thead>
<tr>
<th>User Responses</th>
<th>Occurence</th>
</tr>
</thead>
<tbody>
<tr t-foreach="number_result['most_common']" t-as="row">
<td>
<span t-esc="row[0]"></span>
</td>
<td>
<span t-esc="row[1]"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
<table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
<th>User Responses</th>
</tr>
</thead>
<tbody>
<tr class="d-none" t-foreach="number_result['input_lines']" t-as="user_input">
<td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
<td><span t-field="user_input.value_number"></span><br/></td>
</tr>
</tbody>
</table>
<t t-call="survey.pagination"/>
</div>
</div>
</template>
<!-- Pagination Element -->
<template id="pagination" name="Survey Result">
<t t-set="record_limit" t-value="10"/><!-- Change This record_limit to change number of record per page-->
<ul t-att-id="'pagination_%d' % question.id" class="pagination" t-att-data-question_id="question.id" t-att-data-record_limit="record_limit">
<t t-if="len(text_result) > record_limit">
<li t-foreach="page_range(len(text_result), record_limit)" t-as="num" class="page-item">
<a href="#" class="page-link" t-esc="num"></a>
</li>
</t>
</ul>
</template>
</data>
</odoo>
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data>
<!-- ============================================================ -->
<!-- Errors / Corner case management -->
<!-- ============================================================ -->
<!-- Forbidden error messages-->
<template id="survey_403_page" name="Survey: custom 403 page">
<t t-call="survey.layout">
<div id="wrap">
<div class="container">
<h1 class="mt32">403: Forbidden</h1>
<p>The page you were looking for could not be authorized.</p>
<p>Maybe you were looking for
<a t-attf-href="/web#view_type=form&amp;model=survey.survey&amp;id=#{survey.id}&amp;action=survey.action_survey_form">this page</a> ?
</p>
</div>
</div>
</t>
</template>
<!-- Error: void survey -->
<template id="survey_void_content" name="Survey: void content">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<div class="jumbotron mt32">
<h1><span t-field="survey.title"/> survey is void</h1>
<p>Please make sure you have at least one question in your survey. You also need at least one section if you chose the "Page per section" layout.<br />
<a t-att-href="'/web#view_type=form&amp;model=survey.survey&amp;id=%s&amp;action=survey.action_survey_form' % survey.id"
class="btn btn-secondary"
groups="survey.group_survey_manager">Edit in backend</a>
</p>
</div>
</div>
</div>
</t>
</template>
<!-- Error: auth required -->
<template id="survey_auth_required" name="Survey: login required">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<div class="jumbotron mt32">
<h1>Login required</h1>
<p>This survey is open only to registered people. Please
<a t-att-href="redirect_url">log in</a>.
</p>
</div>
</div>
</div>
</t>
</template>
<!-- Expired (closed) survey page -->
<template id="survey_closed_expired" name="Survey: expired">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<div class="jumbotron mt32">
<h1><span t-field="survey.title"/> survey expired</h1>
<p>This survey is now closed. Thank you for your interest !</p>
</div>
</div>
</div>
</t>
</template>
<!-- Finished (taken and finished) survey page -->
<template id="survey_closed_finished" name="Survey: finished">
<t t-call="survey.layout">
<div class="wrap">
<div class="container">
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<div class="jumbotron mt32">
<h1>Thank you!</h1>
<div t-field="survey.thank_you_message" class="oe_no_empty" />
<div class="row">
<div class="col">
<t t-if="survey.scoring_type != 'no_scoring'">
<div>You scored <t t-esc="answer.quizz_score" />%</div>
<t t-if="answer.quizz_passed">
<div>Congratulations, you have passed the test!</div>
<div t-if="survey.certificate" class="mt16 mb16">
<a role="button"
class="btn btn-primary btn-lg"
t-att-href="'/survey/%s/get_certification' % survey.id">
<i class="fa fa-fw fa-trophy" role="img" aria-label="Download certification" title="Download certification"/>
Download certification
</a>
</div>
</t>
<t t-else="">
<div>Unfortunately, you have failed the test.</div>
</t>
</t>
<t t-call="survey.survey_button_retake"/>
<div t-if="survey.scoring_type != 'scoring_without_answers'">
If you wish, you can <a t-att-href="'/survey/print/%s?answer_token=%s&amp;review=True' % (survey.access_token, answer.token)">review your answers</a>
</div>
</div>
<div class="col-6 text-center" t-if="survey.certification_give_badge and answer.quizz_passed">
<img t-att-src="'/web/image/gamification.badge/%s/image_128' % survey.certification_badge_id.id"/>
<div>You received the badge <span class="font-weight-bold" t-esc="survey.certification_badge_id.name"/>!</div>
</div>
</div>
</div>
</div>
<div class="container o_survey_result p-4" t-if="graph_data">
<div class="tab-content">
<div role="tabpanel" class="tab-pane active survey_graph" t-att-data-quizz_score="answer.quizz_score" t-att-id="'graph_question_%d' % answer.id" t-att-data-question_id="answer.id" data-graph-type="doughnut" t-att-data-graph-data="graph_data">
<canvas id="doughnut_chart"></canvas>
<span class="o_overall_performance"></span>
</div>
</div>
</div>
<div class="oe_structure"/>
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- Tools / Utilities -->
<!-- ============================================================ -->
<template id="survey_button_form_view" name="Survey: back to form view">
<div groups="survey.group_survey_manager" t-ignore="true" class="alert alert-info alert-dismissible rounded-0 mt16 fade show d-print-none css_editable_mode_hidden">
<div t-ignore="true" class="text-center">
<a t-attf-href="/web#view_type=form&amp;model=survey.survey&amp;id=#{survey.id}&amp;action=survey.action_survey_form"><i class="fa fa-fw fa-arrow-right"/><span t-if="answer and answer.test_entry">This is a test survey. </span>Edit Survey.</a>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> &#215; </button>
</div>
</template>
<template id="survey_button_retake" name="Survey: retake button">
<div>
<t t-if="not answer.quizz_passed">
<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, answer.invite_token)" />
<t t-if="attempts_left > 0">
<p><span>Number of attemps left</span>: <span t-esc="attempts_left"></span></p>
<p><a role="button" class="btn btn-primary btn-lg" t-att-href="'/survey/retry/%s/%s' % (survey.access_token, answer.token)">
Retry</a></p>
</t>
</t>
<t t-else="">
<p><a role="button" class="btn btn-primary btn-lg" t-att-href="'/survey/retry/%s/%s' % (survey.access_token, answer.token)">
Retry</a></p>
</t>
</t>
</div>
</template>
</data>
</odoo>
@@ -0,0 +1,363 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="survey_page_statistics" name="Survey: result statistics page">
<t t-call="survey.layout">
<div class="oe_structure" id="oe_structure_survey_result_1"/>
<div class="container o_survey_result">
<t t-call="survey.survey_button_form_view" />
<div class="jumbotron mt32">
<h1><span t-field="survey.title" />
<span style="font-size:1.5em;" t-attf-class="fa fa-bar-chart-o #{'fa-bar-chart-o' if survey.scoring_type == 'no_scoring' else 'fa-trophy' if survey.certificate else 'fa-question-circle-o'} float-right " role="img" aria-label="Chart" title="Chart"/></h1>
<div t-field="survey.description" class="oe_no_empty" />
<h2 t-if="not answers">
Sorry, no one answered this survey yet.
</h2>
</div>
<div t-if="answers" class="card d-print-none">
<div class="card-header"><span class="fa fa-filter"></span> Filters <span t-if="filter_display_data" class="float-right text-primary clear_survey_filter"><i class="fa fa-times"></i> Clear All Filters</span></div>
<div class="card-body">
<span t-if="filter_finish == True">
<span class="badge badge-secondary only_left_radius filter-all">All surveys</span><span class="badge badge-primary only_right_radius filter-finished o_active_filter">Finished surveys</span>
</span>
<span t-if="filter_finish == False">
<span class="badge badge-primary only_left_radius filter-all o_active_filter">All surveys</span><span class="badge badge-secondary only_right_radius filter-finished">Finished surveys</span>
</span>
<span t-foreach="filter_display_data" t-as="filter_data">
<span class="badge badge-primary only_left_radius"><i class="fa fa-filter" role="img" aria-label="Filter" title="Filter"></i></span><span class="badge badge-primary no_radius" t-esc="filter_data['question_text']"></span><span class="badge badge-success only_right_radius" t-esc="' > '.join(filter_data['labels'])"></span>
</span>
</div>
</div>
<div t-if="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers']">
<h1 class="mt16">Results Overview</h1>
<div>Success rate: <mark class="font-weight-bold"><t t-esc="survey_dict['success_rate']"></t>%</mark></div>
<div class="survey_graph" data-graph-type="pie" t-att-data-graph-data="survey_dict['scoring_graph_data']">
<!-- canvas element for drawing pie chart -->
<canvas/>
</div>
<hr/>
</div>
<div t-if="answers" t-foreach="survey_dict['page_ids']" t-as='page_ids'>
<t t-set="page" t-value="page_ids['page']"/>
<h1 class="mt16" t-field='page.title'></h1>
<div t-field="page.description" class="oe_no_empty" />
<hr/>
<div t-foreach="page_ids['question_ids']" t-as='question_ids' class="mt16">
<t t-set="input_summary" t-value="question_ids['input_summary']"/>
<t t-set="question" t-value="question_ids['question']"/>
<t t-set="graph_data" t-value="question_ids['graph_data']"/>
<t t-set="prepare_result" t-value="question_ids['prepare_result']"/>
<t t-set="question_with_correct_answers" t-value="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers'] and question.question_type in ['simple_choice', 'multiple_choice']" />
<h4>
<b>Question </b>
<span t-field='question.question'></span>
<t t-if="question.question_type == 'matrix'">
<small><span class="badge badge-secondary">Matrix: <span t-field='question.matrix_subtype'/></span></small>
</t>
<t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
<small><span t-field='question.question_type' class="badge badge-secondary"></span></small>
</t>
<span class="float-right">
<span class="badge badge-success"><span t-esc="input_summary['answered']"></span> Answered</span>
<span class="badge badge-warning"><span t-esc="input_summary['skipped']"></span> Skipped</span>
</span>
</h4>
<t t-if="question_with_correct_answers">
<t t-set="correct_answers" t-value="question.get_correct_answer_ids()" />
<div t-if="correct_answers">
<t t-if="question.question_type == 'simple_choice'">
<p><span>Correct answer</span>: <span class="font-weight-bold" t-esc="correct_answers[0].value"></span></p>
</t>
<t t-if="question.question_type == 'multiple_choice'">
<span>Correct answers</span>:
<ul>
<li t-foreach="correct_answers" t-as="correct_answer">
<t t-esc="correct_answer.value" />
</li>
</ul>
</t>
</div>
</t>
<t t-if="input_summary['answered'] != 0">
<t t-if="question.description">
<div class="text-muted oe_no_empty" t-field="question.description" />
</t>
<t t-if="question.question_type in ['textbox', 'free_text', 'date', 'datetime']">
<t t-call="survey.question_result_text"></t>
</t>
<t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
<t t-call="survey.question_result_choice"></t>
</t>
<t t-if="question.question_type == 'matrix'">
<t t-call="survey.question_result_matrix"></t>
</t>
<t t-if="question.question_type == 'numerical_box'">
<t t-call="survey.question_result_number"></t>
</t>
</t>
<t t-if="input_summary['answered'] == 0">
<h2 style="padding-top:30px;padding-bottom:30px;text-align:center;" class="text-muted">Sorry, No one answered this question.</h2>
</t>
</div>
</div>
</div>
<div class="oe_structure" id="oe_structure_survey_result_2"/>
</t>
</template>
<!-- Result for free_text,textbox and date -->
<template id="question_result_text" name="Question: text result">
<table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
<th>User Responses</th>
</tr>
</thead>
<tbody>
<t t-set="text_result" t-value="prepare_result"/>
<tr t-foreach="text_result" t-as="user_input">
<td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
<t t-if="question.question_type == 'free_text'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_free_text"></a><br/>
</td>
</t>
<t t-if="question.question_type == 'textbox'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_text"></a><br/>
</td>
</t>
<t t-if="question.question_type == 'date'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_date"></a><br/>
</td>
</t>
<t t-if="question.question_type == 'datetime'">
<td>
<a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)" t-field="user_input.value_datetime"></a><br/>
</td>
</t>
</tr>
</tbody>
</table>
<t t-call="survey.question_pagination"/>
</template>
<template id="question_result_comments" name="Question: text tesult">
<!-- a 'comments' variable must be set an must contain a list of browse records of user input lines -->
<table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<tr t-foreach="comments" t-as="user_input">
<td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
<td>
<span t-field="user_input.value_text"></span><br/>
</td>
</tr>
</tbody>
</table>
</template>
<template id="question_result_choice" name="Question: choice result">
<div>
<!-- Tabs -->
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item" t-if="question.question_type != 'simple_choice'">
<a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-bar-chart-o"></i> Graph
</a>
</li>
<li class="nav-item" t-if="question.question_type == 'simple_choice'">
<a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-bar-chart-o"></i> Pie Chart
</a>
</li>
<li class="nav-item">
<a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
<i class="fa fa-list-alt"></i> Data
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active survey_graph" t-if="question.question_type != 'simple_choice'" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id="question.id" data-graph-type="bar" t-att-data-graph-data="graph_data">
<!-- canvas element for drawing bar chart -->
<canvas/>
</div>
<div role="tabpanel" class="tab-pane active survey_graph" t-if="question.question_type == 'simple_choice'" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id="question.id" data-graph-type="pie" t-att-data-graph-data="graph_data">
<!-- canvas element for drawing pie chart -->
<canvas/>
</div>
<div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
<table class="table table-hover table-sm">
<thead>
<tr>
<th>Answer Choices</th>
<th>User Responses</th>
<th t-if="question_with_correct_answers">Answer Score</th>
</tr>
</thead>
<tbody>
<tr t-foreach="prepare_result['answers']" t-as="user_input">
<td>
<p t-esc="user_input['text']"></p>
</td>
<td class="survey_answer">
<span t-esc="round(user_input['count']*100.0/(input_summary['answered'] or 1),2)"></span> %
<span t-esc="user_input['count']" class="badge badge-primary">Vote</span>
<i class="fa fa-filter text-primary survey_filter" t-att-data-question_id="question.id" t-att-data-answer_id="user_input['answer_id']" role="img" aria-label="Filter question" title="Filter question"/>
</td>
<td t-if="question_with_correct_answers" t-esc="user_input['answer_score']">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- handle comments -->
<div>
<t t-set="comments" t-value="prepare_result['comments']" />
<t t-if="comments">
<t t-call="survey.question_result_comments" />
</t>
</div>
</div>
</template>
<template id="question_result_matrix" name="Question: matrix result">
<t t-set="matrix_result" t-value="prepare_result"/>
<!-- Tabs -->
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item">
<a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-bar-chart"></i>
Graph
</a>
</li>
<li class="nav-item">
<a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
<i class="fa fa-list-alt"></i>
Data
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions survey_graph" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id= "question.id" data-graph-type= "multi_bar" t-att-data-graph-data="graph_data">
<!-- canvas element for drawing Multibar chart -->
<canvas/>
</div>
<div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
<table class="table table-hover table-sm text-right">
<thead>
<tr>
<th></th>
<th class="text-right" t-foreach="matrix_result['answers']" t-as="answer_id">
<span t-esc="matrix_result['answers'][answer_id]"></span>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="matrix_result['rows']" t-as="row_id">
<td>
<span t-esc="matrix_result['rows'][row_id]"></span>
</td>
<td class="survey_answer" t-foreach="matrix_result['answers']" t-as="answer_id">
<span t-esc="round(matrix_result['result'][(row_id,answer_id)]*100.0/(input_summary['answered'] or 1),2)"></span> %
<span class="badge badge-primary" t-esc="matrix_result['result'][(row_id,answer_id)]"></span><i class="fa fa-filter text-primary survey_filter" t-att-data-question_id="question.id" t-att-data-row_id="row_id" t-att-data-answer_id="answer_id" role="img" aria-label="Survey filter" title="Survey filter"></i>
</td>
</tr>
</tbody>
</table>
</div>
<!-- handle comments to matrix -->
<div>
<t t-set="comments" t-value="matrix_result['comments']" />
<t t-if="comments">
<t t-call="survey.question_result_comments" />
</t>
</div>
</div>
</template>
<template id="question_result_number" name="Question:; number result">
<t t-set="number_result" t-value="prepare_result"/>
<t t-set="text_result" t-value="number_result['input_lines']" />
<span class="float-right mt8">
<span class="badge badge-secondary only_left_radius">Sum </span> <span class="badge badge-info only_right_radius" t-esc="number_result['sum']"></span>
<span class="badge badge-secondary only_left_radius">Maximum </span> <span class="badge badge-success only_right_radius" t-esc="number_result['max']"></span>
<span class="badge badge-secondary only_left_radius">Minimum </span> <span class="badge badge-danger only_right_radius" t-esc="number_result['min']"></span>
<span class="badge badge-secondary only_left_radius">Average </span> <span class="badge badge-warning only_right_radius" t-esc="number_result['average']"></span>
</span>
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item">
<a t-att-href="'#most_common_%d' % question.id" t-att-aria-controls="'most_common_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
<i class="fa fa-list-ol"></i>
Most Common <span t-esc="len(number_result['most_common'])"></span>
</a>
</li>
<li class="nav-item">
<a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
<i class="fa fa-list-alt"></i>
All Data
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions" t-att-id="'most_common_%d' % question.id">
<table class="table table-hover table-sm">
<thead>
<tr>
<th>User Responses</th>
<th>Occurence</th>
</tr>
</thead>
<tbody>
<tr t-foreach="number_result['most_common']" t-as="row">
<td>
<span t-esc="row[0]"></span>
</td>
<td>
<span t-esc="row[1]"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
<table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
<th>User Responses</th>
</tr>
</thead>
<tbody>
<tr class="d-none" t-foreach="number_result['input_lines']" t-as="user_input">
<td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.token)"><t t-esc="user_input_index + 1"></t></a></td>
<td><span t-field="user_input.value_number"></span><br/></td>
</tr>
</tbody>
</table>
<t t-call="survey.question_pagination"/>
</div>
</div>
</template>
<template id="question_pagination" name="Survey: statistics pages pagination">
<t t-set="record_limit" t-value="10"/><!-- Change This record_limit to change number of record per page-->
<ul t-att-id="'pagination_%d' % question.id" class="pagination" t-att-data-question_id="question.id" t-att-data-record_limit="record_limit">
<t t-if="len(text_result) > record_limit">
<li t-foreach="page_range(len(text_result), record_limit)" t-as="num" class="page-item">
<a href="#" class="page-link" t-esc="num"></a>
</li>
</t>
</ul>
</template>
</data>
</odoo>
+2 -2
View File
@@ -150,9 +150,9 @@ class SurveyInvite(models.TransientModel):
values['body'] = template.body_html
return super(SurveyInvite, self).create(values)
#------------------------------------------------------
# ------------------------------------------------------
# Wizard validation and send
#------------------------------------------------------
# ------------------------------------------------------
def _prepare_answers(self, partners, emails):
answers = self.env['survey.user_input']
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="survey_templates_inherit_website_slides" inherit_id="survey.sfinished">
<template id="survey_closed_finished_inherit_website_slides" inherit_id="survey.survey_closed_finished">
<xpath expr="//div[hasclass('jumbotron')]/div[last()]" position="after">
<div t-if="channel_url" class="mt16">
<a role="button"