[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:
@@ -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',
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'})
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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&model=survey.survey&id=#{survey.id}&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&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&model=survey.survey&id=%s&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&model=survey.survey&id=#{survey.id}&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"> × </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">&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">&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&model=survey.survey&id=#{survey.id}&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&model=survey.survey&id=%s&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&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&model=survey.survey&id=#{survey.id}&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"> × </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>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user