[MERGE] survey: perform a back 2 basics
PURPOSE Perform a back2basics review of survey to improve daily use, new user onboarding and labelling. SPECIFICATIONS Improve labels of fields and wording in actions. See commit content for proper specifications. Reorder some code to better understand module organization. Order answer lines following question order. Display section in answer lines form view to better understand question and answer organization. Add a correct flag on answer lines and display it in user input embedded view in order to know which line are correct. When finishing a certification survey, add an overall correctness circular graph. Quarters are * correct * partially (if multiple corrects answers on the survey); * incorrect * unanswered Questions without points are not displayed in this graph. LINKS Task 2039068 closes odoo/odoo#36229 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -20,6 +20,10 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class Survey(http.Controller):
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACCESS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _fetch_from_access_token(self, survey_token, answer_token):
|
||||
""" Check that given token matches an answer from the given survey_id.
|
||||
Returns a sudo-ed browse record of survey in order to avoid access rights
|
||||
@@ -182,6 +186,10 @@ class Survey(http.Controller):
|
||||
'deadline': answer.deadline,
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TAKING SURVEY ROUTES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route('/survey/start/<string:survey_token>', type='http', auth='public', website=True)
|
||||
def survey_start(self, survey_token, answer_token=None, email=False, **post):
|
||||
""" Start a survey by providing
|
||||
@@ -423,6 +431,10 @@ class Survey(http.Controller):
|
||||
|
||||
return json.dumps(ret)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# COMPLETED SURVEY ROUTES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route('/survey/print/<string:survey_token>', type='http', auth='public', website=True)
|
||||
def survey_print(self, survey_token, review=False, answer_token=None, **post):
|
||||
'''Display an survey in printable view; if <answer_token> is set, it will
|
||||
@@ -639,4 +651,12 @@ class Survey(http.Controller):
|
||||
values = {'survey': survey, 'answer': answer}
|
||||
if token:
|
||||
values['token'] = token
|
||||
if survey.scoring_type != 'no_scoring' and survey.certificate:
|
||||
answer_perf = survey._get_answers_correctness(answer)[answer]
|
||||
values['graph_data'] = json.dumps([
|
||||
{"text": "Correct", "count": answer_perf['correct']},
|
||||
{"text": "Partially", "count": answer_perf['partial']},
|
||||
{"text": "Incorrect", "count": answer_perf['incorrect']},
|
||||
{"text": "Unanswered", "count": answer_perf['skipped']}
|
||||
])
|
||||
return values
|
||||
|
||||
@@ -8,7 +8,7 @@ from itertools import product
|
||||
from werkzeug import urls
|
||||
import random
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
|
||||
@@ -25,7 +25,7 @@ class Survey(models.Model):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
# description
|
||||
title = fields.Char('Title', required=True, translate=True)
|
||||
title = fields.Char('Survey Title', required=True, 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)
|
||||
@@ -188,7 +188,10 @@ class Survey(models.Model):
|
||||
if not self.users_login_required or not self.certificate:
|
||||
self.certification_give_badge = False
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
survey = super(Survey, self).create(vals)
|
||||
@@ -202,12 +205,15 @@ class Survey(models.Model):
|
||||
return self.sudo()._handle_certification_badges(vals)
|
||||
return result
|
||||
|
||||
# Public methods #
|
||||
def copy_data(self, default=None):
|
||||
title = _("%s (copy)") % (self.title)
|
||||
default = dict(default or {}, title=title)
|
||||
return super(Survey, self).copy_data(default)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TECHNICAL
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _create_answer(self, user=False, partner=False, email=False, test_entry=False, check_attempts=True, **additional_vals):
|
||||
""" Main entry point to get a token back or create a new one. This method
|
||||
does check for current user access in order to explicitely validate
|
||||
@@ -278,6 +284,60 @@ class Survey(models.Model):
|
||||
if check_attempts and not self._has_attempts_left(partner or (user and user.partner_id), email, invite_token):
|
||||
raise UserError(_('No attempts left.'))
|
||||
|
||||
def _prepare_answer_questions(self):
|
||||
""" Will generate the questions for a randomized survey.
|
||||
It uses the random_questions_count of every sections of the survey to
|
||||
pick a random number of questions and returns the merged recordset """
|
||||
self.ensure_one()
|
||||
|
||||
questions = self.env['survey.question']
|
||||
|
||||
for page in self.page_ids:
|
||||
if self.questions_selection == 'all':
|
||||
questions |= page.question_ids
|
||||
else:
|
||||
if page.random_questions_count > 0 and len(page.question_ids) > page.random_questions_count:
|
||||
questions = questions.concat(*random.sample(page.question_ids, page.random_questions_count))
|
||||
else:
|
||||
questions |= page.question_ids
|
||||
|
||||
if not questions:
|
||||
questions = self.question_ids
|
||||
|
||||
return questions
|
||||
|
||||
def _has_attempts_left(self, partner, email, invite_token):
|
||||
self.ensure_one()
|
||||
|
||||
if (self.access_mode != 'public' or self.users_login_required) and self.is_attempts_limited:
|
||||
return self._get_number_of_attempts_lefts(partner, email, invite_token) > 0
|
||||
|
||||
return True
|
||||
|
||||
def _get_number_of_attempts_lefts(self, partner, email, invite_token):
|
||||
""" Returns the number of attempts left. """
|
||||
self.ensure_one()
|
||||
|
||||
domain = [
|
||||
('survey_id', '=', self.id),
|
||||
('test_entry', '=', False),
|
||||
('state', '=', 'done')
|
||||
]
|
||||
|
||||
if partner:
|
||||
domain = expression.AND([domain, [('partner_id', '=', partner.id)]])
|
||||
else:
|
||||
domain = expression.AND([domain, [('email', '=', email)]])
|
||||
|
||||
if invite_token:
|
||||
domain = expression.AND([domain, [('invite_token', '=', invite_token)]])
|
||||
|
||||
return self.attempts_limit - self.env['survey.user_input'].search_count(domain)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def next_page_or_question(self, user_input, page_or_question_id, go_back=False):
|
||||
""" The next page to display to the user, knowing that page_id is the id
|
||||
@@ -329,6 +389,115 @@ class Survey(models.Model):
|
||||
else:
|
||||
return (pages_or_questions[current_page_index + 1][1], False)
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
def action_open(self):
|
||||
self.write({'state': 'open'})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
|
||||
def action_start_survey(self):
|
||||
""" Open the website page with the survey form """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Start Survey",
|
||||
'target': 'self',
|
||||
'url': self.public_url + trail
|
||||
}
|
||||
|
||||
def action_send_survey(self):
|
||||
""" Open a window to compose an email, pre-filled with the survey message """
|
||||
# Ensure that this survey has at least one page with at least one question.
|
||||
if (not self.page_ids and self.questions_layout == 'page_per_section') or not self.question_ids:
|
||||
raise UserError(_('You cannot send an invitation for a survey that has no questions.'))
|
||||
|
||||
if self.state == 'closed':
|
||||
raise UserError(_("You cannot send invitations for closed surveys."))
|
||||
|
||||
template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
self.env.context,
|
||||
default_survey_id=self.id,
|
||||
default_use_template=bool(template),
|
||||
default_template_id=template and template.id or False,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'survey.invite',
|
||||
'target': 'new',
|
||||
'context': local_context,
|
||||
}
|
||||
|
||||
def action_print_survey(self):
|
||||
""" Open the website page with the survey printable view """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Print Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/print/%s%s' % (self.access_token, trail)
|
||||
}
|
||||
|
||||
def action_result_survey(self):
|
||||
""" Open the website page with the survey results view """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Results of the Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/results/%s' % self.id
|
||||
}
|
||||
|
||||
def action_test_survey(self):
|
||||
''' Open the website page with the survey form into test mode'''
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Test Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/test/%s' % self.access_token,
|
||||
}
|
||||
|
||||
def action_survey_user_input_completed(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_completed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input_certified(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_quizz_passed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0]})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GRAPH / RESULTS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def filter_input_ids(self, filters, finished=False):
|
||||
"""If user applies any filters, then this function returns list of
|
||||
filtered user_input_id and label's strings for display data in web.
|
||||
@@ -453,6 +622,43 @@ class Survey(models.Model):
|
||||
|
||||
return result
|
||||
|
||||
def _get_answers_correctness(self, user_answers):
|
||||
if not user_answers.mapped('survey_id') == self:
|
||||
raise UserError(_('Invalid performance computation'))
|
||||
|
||||
res = dict((user_answer, {
|
||||
'correct': 0,
|
||||
'incorrect': 0,
|
||||
'partial': 0,
|
||||
'skipped': 0,
|
||||
}) for user_answer in user_answers)
|
||||
|
||||
scored_questions = self.question_ids.filtered(
|
||||
lambda question: question.question_type in ['simple_choice', 'multiple_choice']
|
||||
)
|
||||
|
||||
for question in scored_questions:
|
||||
question_answer_correct = question.labels_ids.filtered(lambda answer: answer.is_correct)
|
||||
for user_answer in user_answers:
|
||||
user_answer_lines_question = user_answer.user_input_line_ids.filtered(lambda line: line.question_id == question)
|
||||
user_answer_correct = user_answer_lines_question.filtered(lambda line: line.answer_is_correct and not line.skipped).mapped('value_suggested')
|
||||
user_answer_incorrect = user_answer_lines_question.filtered(lambda line: not line.answer_is_correct and not line.skipped)
|
||||
|
||||
if user_answer_correct == question_answer_correct:
|
||||
res[user_answer]['correct'] += 1
|
||||
elif user_answer_correct and user_answer_correct < question_answer_correct:
|
||||
res[user_answer]['partial'] += 1
|
||||
if not user_answer_correct and user_answer_incorrect:
|
||||
res[user_answer]['incorrect'] += 1
|
||||
if not user_answer_correct and not user_answer_incorrect:
|
||||
res[user_answer]['skipped'] += 1
|
||||
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GAMIFICATION / BADGES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _create_certification_badge_trigger(self):
|
||||
self.ensure_one()
|
||||
goal = self.env['gamification.goal.definition'].create({
|
||||
@@ -502,160 +708,3 @@ class Survey(models.Model):
|
||||
# delete all challenges and goals because not needed anymore (challenge lines are deleted in cascade)
|
||||
challenges_to_delete.unlink()
|
||||
goals_to_delete.unlink()
|
||||
|
||||
# Actions
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
def action_open(self):
|
||||
self.write({'state': 'open'})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
|
||||
def action_start_survey(self):
|
||||
""" Open the website page with the survey form """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Start Survey",
|
||||
'target': 'self',
|
||||
'url': self.public_url + trail
|
||||
}
|
||||
|
||||
def action_send_survey(self):
|
||||
""" Open a window to compose an email, pre-filled with the survey message """
|
||||
# Ensure that this survey has at least one page with at least one question.
|
||||
if (not self.page_ids and self.questions_layout == 'page_per_section') or not self.question_ids:
|
||||
raise UserError(_('You cannot send an invitation for a survey that has no questions.'))
|
||||
|
||||
if self.state == 'closed':
|
||||
raise UserError(_("You cannot send invitations for closed surveys."))
|
||||
|
||||
template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
self.env.context,
|
||||
default_survey_id=self.id,
|
||||
default_use_template=bool(template),
|
||||
default_template_id=template and template.id or False,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'survey.invite',
|
||||
'target': 'new',
|
||||
'context': local_context,
|
||||
}
|
||||
|
||||
def action_print_survey(self):
|
||||
""" Open the website page with the survey printable view """
|
||||
self.ensure_one()
|
||||
token = self.env.context.get('survey_token')
|
||||
trail = "?answer_token=%s" % token if token else ""
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Print Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/print/%s%s' % (self.access_token, trail)
|
||||
}
|
||||
|
||||
def action_result_survey(self):
|
||||
""" Open the website page with the survey results view """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Results of the Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/results/%s' % self.id
|
||||
}
|
||||
|
||||
def action_test_survey(self):
|
||||
''' Open the website page with the survey form into test mode'''
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'name': "Test Survey",
|
||||
'target': 'self',
|
||||
'url': '/survey/test/%s' % self.access_token,
|
||||
}
|
||||
|
||||
def action_survey_user_input_completed(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_completed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input_certified(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0],
|
||||
'search_default_quizz_passed': 1})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def action_survey_user_input(self):
|
||||
action_rec = self.env.ref('survey.action_survey_user_input_notest')
|
||||
action = action_rec.read()[0]
|
||||
ctx = dict(self.env.context)
|
||||
ctx.update({'search_default_survey_id': self.ids[0]})
|
||||
action['context'] = ctx
|
||||
return action
|
||||
|
||||
def _has_attempts_left(self, partner, email, invite_token):
|
||||
self.ensure_one()
|
||||
|
||||
if (self.access_mode != 'public' or self.users_login_required) and self.is_attempts_limited:
|
||||
return self._get_number_of_attempts_lefts(partner, email, invite_token) > 0
|
||||
|
||||
return True
|
||||
|
||||
def _get_number_of_attempts_lefts(self, partner, email, invite_token):
|
||||
""" Returns the number of attempts left. """
|
||||
self.ensure_one()
|
||||
|
||||
domain = [
|
||||
('survey_id', '=', self.id),
|
||||
('test_entry', '=', False),
|
||||
('state', '=', 'done')
|
||||
]
|
||||
|
||||
if partner:
|
||||
domain = expression.AND([domain, [('partner_id', '=', partner.id)]])
|
||||
else:
|
||||
domain = expression.AND([domain, [('email', '=', email)]])
|
||||
|
||||
if invite_token:
|
||||
domain = expression.AND([domain, [('invite_token', '=', invite_token)]])
|
||||
|
||||
return self.attempts_limit - self.env['survey.user_input'].search_count(domain)
|
||||
|
||||
def _prepare_answer_questions(self):
|
||||
""" Will generate the questions for a randomized survey.
|
||||
It uses the random_questions_count of every sections of the survey to
|
||||
pick a random number of questions and returns the merged recordset """
|
||||
self.ensure_one()
|
||||
|
||||
questions = self.env['survey.question']
|
||||
|
||||
for page in self.page_ids:
|
||||
if self.questions_selection == 'all':
|
||||
questions |= page.question_ids
|
||||
else:
|
||||
if page.random_questions_count > 0 and len(page.question_ids) > page.random_questions_count:
|
||||
questions = questions.concat(*random.sample(page.question_ids, page.random_questions_count))
|
||||
else:
|
||||
questions |= page.question_ids
|
||||
|
||||
if not questions:
|
||||
questions = self.question_ids
|
||||
|
||||
return questions
|
||||
|
||||
@@ -199,11 +199,15 @@ class SurveyUserInputLine(models.Model):
|
||||
_name = 'survey.user_input_line'
|
||||
_description = 'Survey User Input Line'
|
||||
_rec_name = 'user_input_id'
|
||||
_order = 'question_sequence,id'
|
||||
|
||||
# survey data
|
||||
user_input_id = fields.Many2one('survey.user_input', string='User Input', ondelete='cascade', required=True)
|
||||
question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True)
|
||||
page_id = fields.Many2one(related='question_id.page_id', string="Page", readonly=False)
|
||||
survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False)
|
||||
question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True)
|
||||
page_id = fields.Many2one(related='question_id.page_id', string="Section", readonly=False)
|
||||
question_sequence = fields.Integer('Sequence', related='question_id.sequence', store=True)
|
||||
# answer
|
||||
skipped = fields.Boolean('Skipped')
|
||||
answer_type = fields.Selection([
|
||||
('text', 'Text'),
|
||||
@@ -219,7 +223,16 @@ class SurveyUserInputLine(models.Model):
|
||||
value_free_text = fields.Text('Free Text answer')
|
||||
value_suggested = fields.Many2one('survey.label', string="Suggested answer")
|
||||
value_suggested_row = fields.Many2one('survey.label', string="Row answer")
|
||||
answer_score = fields.Float('Score given for this choice')
|
||||
answer_score = fields.Float('Score')
|
||||
answer_is_correct = fields.Boolean('Correct', compute='_compute_answer_is_correct')
|
||||
|
||||
@api.depends('value_suggested', 'question_id')
|
||||
def _compute_answer_is_correct(self):
|
||||
for answer in self:
|
||||
if answer.value_suggested and answer.question_id.question_type in ['simple_choice', 'multiple_choice']:
|
||||
answer.answer_is_correct = answer.value_suggested.is_correct
|
||||
else:
|
||||
answer.answer_is_correct = False
|
||||
|
||||
@api.constrains('skipped', 'answer_type')
|
||||
def _answered_or_skipped(self):
|
||||
|
||||
@@ -2,6 +2,7 @@ odoo.define('survey.result', function (require) {
|
||||
'use strict';
|
||||
|
||||
require('web.dom_ready');
|
||||
var _t = require('web.core')._t;
|
||||
|
||||
if(!$('.js_surveyresult').length) {
|
||||
return Promise.reject("DOM doesn't contain '.js_surveyresult'");
|
||||
@@ -159,6 +160,35 @@ if(!$('.js_surveyresult').length) {
|
||||
return chartConfig;
|
||||
}
|
||||
|
||||
//initialize doughnut Chart
|
||||
function init_doughnut_chart(graph_data, quizz_score){
|
||||
var data = graph_data.map(function (point) {
|
||||
return point.count;
|
||||
});
|
||||
var chartConfig = {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: graph_data.map(function (point) {
|
||||
return point.text;
|
||||
}),
|
||||
datasets: [{
|
||||
label: '',
|
||||
data: data,
|
||||
backgroundColor: data.map(function (val, index) {
|
||||
return D3_COLORS[index % 20];
|
||||
}),
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: true,
|
||||
text: _.str.sprintf(_t("Overall Performance %.2f%s"), parseFloat(quizz_score), '%'),
|
||||
},
|
||||
}
|
||||
};
|
||||
return chartConfig;
|
||||
}
|
||||
|
||||
//load chart to svg element chart:initialized chart, response:AJAX response, quistion_id:if of survey question, tick_limit:text length limit
|
||||
function load_chart(chartConfig, containerSelector){
|
||||
var $container = $(containerSelector).css({position: 'relative'});
|
||||
@@ -187,6 +217,11 @@ if(!$('.js_surveyresult').length) {
|
||||
chartConfig = init_pie_chart(graph_data);
|
||||
return load_chart(chartConfig, containerSelector);
|
||||
}
|
||||
else if (graph_type === 'doughnut') {
|
||||
var quizz_score = $(graph).attr("quizz-score") || 0.0;
|
||||
chartConfig = init_doughnut_chart(graph_data, quizz_score);
|
||||
return load_chart(chartConfig, containerSelector);
|
||||
}
|
||||
});
|
||||
|
||||
var $scoringResultsChart = $('#scoring_results_chart');
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<div class="oe_title" style="width: 100%;">
|
||||
<label for="title" class="oe_edit_only"/>
|
||||
<h1><field name="title" placeholder="Survey Title"/></h1>
|
||||
<h1><field name="title" placeholder="e.g. Satisfaction Survey"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
@@ -257,7 +257,7 @@
|
||||
</p><p>
|
||||
You can create surveys for different purposes: customer opinion, services feedback, recruitment interviews, employee's periodical evaluations, marketing campaigns, etc.
|
||||
</p><p>
|
||||
Design easily your survey, send invitations to answer by email and analyze answers.
|
||||
Design easily your survey, send invitations and analyze answers.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -57,6 +57,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container js_surveyresult p-4" t-if="graph_data">
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active survey_graph" t-att-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-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>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- USER INPUTS -->
|
||||
<record id="survey_user_input_search" model="ir.ui.view">
|
||||
<field name="name">survey_user_input_search</field>
|
||||
<record id="survey_user_input_view_search" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.search</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Survey">
|
||||
@@ -26,8 +26,9 @@
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="survey_user_input_form">
|
||||
<field name="name">survey_user_input_form</field>
|
||||
|
||||
<record id="survey_user_input_view_form" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.form</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Survey User inputs" create="false">
|
||||
@@ -65,19 +66,23 @@
|
||||
</group>
|
||||
<field name="user_input_line_ids" mode="tree" attrs="{'readonly': False}">
|
||||
<tree>
|
||||
<field name="question_sequence" invisible="1"/>
|
||||
<field name="question_id"/>
|
||||
<field name="page_id"/>
|
||||
<field name="answer_type"/>
|
||||
<field name="skipped"/>
|
||||
<field name="create_date"/>
|
||||
<field name="answer_score" groups="base.group_no_one"/>
|
||||
<field name="answer_is_correct"/>
|
||||
<field name="answer_score"/>
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="survey_user_input_tree">
|
||||
<field name="name">survey_user_input_tree</field>
|
||||
|
||||
<record id="survey_user_input_view_tree" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.tree</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Survey User inputs" decoration-muted="test_entry == True" create="false">
|
||||
@@ -96,8 +101,8 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_survey_user_input_kanban" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.kanban</field>
|
||||
<record id="survey_user_input_viuew_kanban" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.view.kanban</field>
|
||||
<field name="model">survey.user_input</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban create="false">
|
||||
@@ -135,8 +140,8 @@
|
||||
<field name="name">Answers</field>
|
||||
<field name="res_model">survey.user_input</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="survey_user_input_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_search"/>
|
||||
<field name="view_id" ref="survey_user_input_view_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_view_search"/>
|
||||
<field name="context">{'search_default_group_by_survey': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
@@ -148,8 +153,8 @@
|
||||
<field name="name">Answers</field>
|
||||
<field name="res_model">survey.user_input</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="survey_user_input_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_search"/>
|
||||
<field name="view_id" ref="survey_user_input_view_tree"></field>
|
||||
<field name="search_view_id" ref="survey_user_input_view_search"/>
|
||||
<field name="context">{'search_default_group_by_survey': True}</field>
|
||||
<field name="domain">[('test_entry', '=', False)]</field>
|
||||
<field name="help" type="html">
|
||||
|
||||
Reference in New Issue
Block a user