diff --git a/addons/survey/__manifest__.py b/addons/survey/__manifest__.py
index a486acd5329..8eae1194491 100644
--- a/addons/survey/__manifest__.py
+++ b/addons/survey/__manifest__.py
@@ -14,7 +14,7 @@ each question may have multiple answers. Different users may give different
answers of question and according to that survey is done. Partners are also
sent mails with personal token for the invitation of the survey.
""",
- 'summary': 'Create surveys and analyze answers',
+ 'summary': 'Send your surveys or share them live.',
'website': 'https://www.odoo.com/app/surveys',
'depends': [
'auth_signup',
@@ -26,7 +26,6 @@ sent mails with personal token for the invitation of the survey.
'views/survey_report_templates.xml',
'views/survey_reports.xml',
'data/mail_template_data.xml',
- 'data/ir_actions_data.xml',
'security/survey_security.xml',
'security/ir.model.access.csv',
'views/survey_menus.xml',
@@ -96,6 +95,7 @@ sent mails with personal token for the invitation of the survey.
'survey/static/src/js/survey_renderers.js',
'survey/static/src/js/survey_views.js',
'survey/static/src/scss/survey_survey_views.scss',
+ 'survey/static/src/scss/survey_question_views.scss',
],
'web.assets_tests': [
'survey/static/tests/**/*',
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index 74027e25464..e0af41aff08 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -191,7 +191,7 @@ class Survey(http.Controller):
values = {'survey': survey, 'answer': answer}
if token:
values['token'] = token
- if survey.scoring_type != 'no_scoring' and survey.certification:
+ if survey.scoring_type != 'no_scoring':
values['graph_data'] = json.dumps(answer._prepare_statistics()[answer])
return values
diff --git a/addons/survey/data/ir_actions_data.xml b/addons/survey/data/ir_actions_data.xml
deleted file mode 100644
index 7e0473fdbeb..00000000000
--- a/addons/survey/data/ir_actions_data.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- Survey: Clean test answers
- ir.actions.server
-
-
- code
-
-if records:
- env['survey.user_input'].search([('survey_id', 'in', records.ids), ('test_entry', '=', 'True')]).unlink()
-
-
-
-
\ No newline at end of file
diff --git a/addons/survey/data/mail_template_data.xml b/addons/survey/data/mail_template_data.xml
index 280c9a77097..e850a1902e7 100644
--- a/addons/survey/data/mail_template_data.xml
+++ b/addons/survey/data/mail_template_data.xml
@@ -30,7 +30,12 @@
Please answer the survey for 05/05/2021.
- Thank you for your participation.
+
+ We wish you good luck!
+
+
+ Thank you in advance for your participation.
+
@@ -62,10 +67,11 @@
|
Dear participant
- Here is, in attachment, your certification document for
- Feedback Form
+ Please find attached your
+ Furniture Creation
+ certification
- Congratulations for succeeding the test!
+ Congratulations for passing the test!
|
diff --git a/addons/survey/data/survey_demo_conditional.xml b/addons/survey/data/survey_demo_conditional.xml
index 740337ee0d1..138bd5d01d3 100644
--- a/addons/survey/data/survey_demo_conditional.xml
+++ b/addons/survey/data/survey_demo_conditional.xml
@@ -406,7 +406,6 @@
Which painting/drawing was not made by Pablo Picasso ?
simple_choice
-
diff --git a/addons/survey/data/survey_demo_quiz.xml b/addons/survey/data/survey_demo_quiz.xml
index 3b295637d9d..b7818832bd9 100644
--- a/addons/survey/data/survey_demo_quiz.xml
+++ b/addons/survey/data/survey_demo_quiz.xml
@@ -331,7 +331,6 @@
31
simple_choice
6
-
@@ -367,7 +366,6 @@
32
simple_choice
6
-
@@ -429,7 +427,6 @@
34
simple_choice
-
@@ -471,7 +468,6 @@
35
multiple_choice
6
-
diff --git a/addons/survey/models/survey_question.py b/addons/survey/models/survey_question.py
index d3fcc78473b..e092c537ea7 100644
--- a/addons/survey/models/survey_question.py
+++ b/addons/survey/models/survey_question.py
@@ -49,7 +49,7 @@ class SurveyQuestion(models.Model):
def default_get(self, fields):
defaults = super(SurveyQuestion, self).default_get(fields)
if (not fields or 'question_type' in fields):
- defaults['question_type'] = False if defaults.get('is_page') == True else 'text_box'
+ defaults['question_type'] = False if defaults.get('is_page') else 'simple_choice'
return defaults
# question generic data
@@ -70,18 +70,18 @@ class SurveyQuestion(models.Model):
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,
+ '# Questions Randomly Picked', default=1,
help="Used on randomized sections to take X random questions from all the questions of that section.")
# question specific
page_id = fields.Many2one('survey.question', string='Page', compute="_compute_page_id", store=True)
question_type = fields.Selection([
+ ('simple_choice', 'Multiple choice: only one answer'),
+ ('multiple_choice', 'Multiple choice: multiple answers allowed'),
('text_box', 'Multiple Lines Text Box'),
('char_box', 'Single Line Text Box'),
('numerical_box', 'Numerical Value'),
('date', 'Date'),
('datetime', 'Datetime'),
- ('simple_choice', 'Multiple choice: only one answer'),
- ('multiple_choice', 'Multiple choice: multiple answers allowed'),
('matrix', 'Matrix')], string='Question Type',
compute='_compute_question_type', readonly=False, store=True)
is_scored_question = fields.Boolean(
@@ -104,7 +104,6 @@ class SurveyQuestion(models.Model):
suggested_answer_ids = fields.One2many(
'survey.question.answer', 'question_id', string='Types of answers', copy=True,
help='Labels used for proposed choices: simple choice, multiple choice and columns of matrix')
- allow_value_image = fields.Boolean('Images on answers', help='Display images in addition to answer label. Valid only for simple / multiple choice questions.')
# -- matrix
matrix_subtype = fields.Selection([
('simple', 'One choice per row'),
@@ -125,7 +124,7 @@ class SurveyQuestion(models.Model):
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')
# question validation
- validation_required = fields.Boolean('Validate entry')
+ validation_required = fields.Boolean('Validate entry', compute='_compute_validation_required', readonly=False, store=True)
validation_email = fields.Boolean('Input must be an email')
validation_length_min = fields.Integer('Minimum Text Length', default=0)
validation_length_max = fields.Integer('Maximum Text Length', default=0)
@@ -260,6 +259,12 @@ class SurveyQuestion(models.Model):
if question.question_type != 'char_box':
question.save_as_nickname = False
+ @api.depends('question_type')
+ def _compute_validation_required(self):
+ for question in self:
+ if not question.validation_required or question.question_type not in ['char_box', 'numerical_box', 'date', 'datetime']:
+ question.validation_required = False
+
@api.depends('is_conditional')
def _compute_triggering_question_id(self):
""" Used as an 'onchange' : Reset the triggering question if user uncheck 'Conditional Display'
@@ -584,13 +589,17 @@ class SurveyQuestionAnswer(models.Model):
_order = 'sequence, id'
_description = 'Survey Label'
+ # question and question related fields
question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade')
matrix_question_id = fields.Many2one('survey.question', string='Question (as matrix row)', ondelete='cascade')
+ question_type = fields.Selection(related='question_id.question_type')
sequence = fields.Integer('Label Sequence order', default=10)
+ scoring_type = fields.Selection(related='question_id.scoring_type')
+ # answer related fields
value = fields.Char('Suggested value', translate=True, required=True)
value_image = fields.Image('Image', max_width=256, max_height=256)
- 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")
+ is_correct = fields.Boolean('Correct')
+ answer_score = fields.Float('Score', help="A positive score indicates a correct choice; a negative or null score indicates a wrong answer")
@api.constrains('question_id', 'matrix_question_id')
def _check_question_not_empty(self):
diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py
index 31e42b53c9e..91e21f42a31 100644
--- a/addons/survey/models/survey_survey.py
+++ b/addons/survey/models/survey_survey.py
@@ -70,15 +70,15 @@ class Survey(models.Model):
('one_page', 'One page with all the questions'),
('page_per_section', 'One page per section'),
('page_per_question', 'One page per question')],
- string="Layout", required=True, default='one_page')
+ string="Pagination", required=True, default='one_page')
questions_selection = fields.Selection([
('all', 'All questions'),
- ('random', 'Randomized per section')],
- string="Selection", required=True, default='all',
+ ('random', 'Randomized per Section')],
+ string="Question Selection", required=True, default='all',
help="If randomized is selected, you can configure the number of random questions by section. This mode is ignored in live session.")
progression_mode = fields.Selection([
- ('percent', 'Percentage'),
- ('number', 'Number')], string='Progression Mode', default='percent',
+ ('percent', 'Percentage left'),
+ ('number', 'Number')], string='Display Progress as', default='percent',
help="If Number is selected, it will display the number of questions answered on the total number of question to answer.")
# attendees
user_input_ids = fields.One2many('survey.user_input', 'survey_id', string='User responses', readonly=True, groups='survey.group_survey_user')
@@ -88,23 +88,23 @@ class Survey(models.Model):
('token', 'Invited people only')], string='Access Mode',
default='public', required=True)
access_token = fields.Char('Access Token', default=lambda self: self._get_default_access_token(), copy=False)
- users_login_required = fields.Boolean('Login Required', help="If checked, users have to login before answering even with a valid token.")
+ users_login_required = fields.Boolean('Require Login', help="If checked, users have to login before answering even with a valid token.")
users_can_go_back = fields.Boolean('Users can go back', help="If checked, users can go back to previous pages.")
users_can_signup = fields.Boolean('Users can signup', compute='_compute_users_can_signup')
# statistics
answer_count = fields.Integer("Registered", compute="_compute_survey_statistic")
answer_done_count = fields.Integer("Attempts", compute="_compute_survey_statistic")
- answer_score_avg = fields.Float("Avg Score %", compute="_compute_survey_statistic")
+ answer_score_avg = fields.Float("Avg Score (%)", compute="_compute_survey_statistic")
answer_duration_avg = fields.Float("Average Duration", compute="_compute_answer_duration_avg", help="Average duration of the survey (in hours)")
success_count = fields.Integer("Success", compute="_compute_survey_statistic")
- success_ratio = fields.Integer("Success Ratio", compute="_compute_survey_statistic")
+ success_ratio = fields.Integer("Success Ratio (%)", compute="_compute_survey_statistic")
# scoring
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')
- scoring_success_min = fields.Float('Success %', default=80.0)
+ string="Scoring", required=True, store=True, readonly=False, compute="_compute_scoring_type", precompute=True)
+ scoring_success_min = fields.Float('Required Score (%)', default=80.0)
# attendees context: attempts and time limitation
is_attempts_limited = fields.Boolean('Limited number of attempts', help="Check this option if you want to limit the number of attempts per user",
compute="_compute_is_attempts_limited", store=True, readonly=False)
@@ -113,9 +113,9 @@ class Survey(models.Model):
time_limit = fields.Float("Time limit (minutes)", default=10)
# certification
certification = fields.Boolean('Is a Certification', compute='_compute_certification',
- readonly=False, store=True)
+ readonly=False, store=True, precompute=True)
certification_mail_template_id = fields.Many2one(
- 'mail.template', 'Email Template',
+ 'mail.template', 'Certified Email Template',
domain="[('model', '=', 'survey.user_input')]",
help="Automated email sent to the user when he succeeds the certification, containing his certification document.")
certification_report_layout = fields.Selection([
@@ -324,16 +324,24 @@ class Survey(models.Model):
not survey.certification:
survey.certification_give_badge = False
+ @api.depends('certification')
+ def _compute_scoring_type(self):
+ for survey in self:
+ if survey.certification and survey.scoring_type not in ['scoring_without_answers', 'scoring_with_answers']:
+ survey.scoring_type = 'scoring_without_answers'
+ elif not survey.scoring_type:
+ survey.scoring_type = 'no_scoring'
+
# ------------------------------------------------------------
# CRUD
# ------------------------------------------------------------
- @api.model
- def create(self, vals):
- survey = super(Survey, self).create(vals)
- if vals.get('certification_give_badge'):
- survey.sudo()._create_certification_badge_trigger()
- return survey
+ @api.model_create_multi
+ def create(self, vals_list):
+ surveys = super(Survey, self).create(vals_list)
+ for survey_sudo in surveys.filtered(lambda survey: survey.certification_give_badge).sudo():
+ survey_sudo._create_certification_badge_trigger()
+ return surveys
def write(self, vals):
result = super(Survey, self).write(vals)
diff --git a/addons/survey/security/ir.model.access.csv b/addons/survey/security/ir.model.access.csv
index 39fec6457fd..124e9f901a4 100644
--- a/addons/survey/security/ir.model.access.csv
+++ b/addons/survey/security/ir.model.access.csv
@@ -17,7 +17,7 @@ access_survey_user_input_survey_user,survey.user_input.survey.user,model_survey_
access_survey_user_input_survey_manager,survey.user_input.survey.manager,model_survey_user_input,group_survey_manager,1,1,1,1
access_survey_user_input_line_all,survey.user_input.line.all,model_survey_user_input_line,,0,0,0,0
access_survey_user_input_line_user,survey.user_input.line.user,model_survey_user_input_line,base.group_user,0,0,0,0
-access_survey_user_input_line_survey_user,survey.user_input.line.survey.user,model_survey_user_input_line,group_survey_user,1,1,1,1
+access_survey_user_input_line_survey_user,survey.user_input.line.survey.user,model_survey_user_input_line,group_survey_user,1,0,0,0
access_survey_user_input_line_survey_manager,survey.user_input.line.survey.manager,model_survey_user_input_line,group_survey_manager,1,1,1,1
access_gamification_badge_survey_user,gamification.badge.survey.user,model_gamification_badge,group_survey_user,1,1,1,1
access_survey_invite,access.survey.invite,model_survey_invite,survey.group_survey_user,1,1,1,0
diff --git a/addons/survey/static/src/css/survey_templates_result.css b/addons/survey/static/src/css/survey_templates_result.css
index dda0022abe6..fcfa02c86c7 100644
--- a/addons/survey/static/src/css/survey_templates_result.css
+++ b/addons/survey/static/src/css/survey_templates_result.css
@@ -54,24 +54,3 @@
width: 1150px;
}
}
-
-.o_preview_questions {
- border: 3px solid #C9C6C6;
- width: auto;
- padding: 10px 30px 5px;
- margin-top: 15px;
- color: #C9C6C6;
-}
-
-.o_preview_questions .o_datetime {
- border: 1px solid #D8D7D7;
- margin-bottom: 5px;
-}
-
-.o_preview_questions .o_matrix_head {
- border-bottom: 1px solid #D8D7D7;
-}
-
-.o_preview_questions .o_matrix_row {
- border-top: 1px solid #D8D7D7;
-}
diff --git a/addons/survey/static/src/js/fields_section_one2many.js b/addons/survey/static/src/js/fields_section_one2many.js
index 80a637bc390..1586d48af14 100644
--- a/addons/survey/static/src/js/fields_section_one2many.js
+++ b/addons/survey/static/src/js/fields_section_one2many.js
@@ -51,7 +51,11 @@ var SectionListRenderer = ListRenderer.extend({
if (record.data.questions_selection === "random") {
nbrColumns--;
}
+ // Render empty cells for buttons to avoid having unaligned elements
+ nbrColumns -= this.columns.filter(elem => elem.tag === "button_group").length;
$cell.attr('colspan', nbrColumns);
+ } else if (node.tag === "button_group") {
+ $cell.addClass('o_invisible_modifier');
} else {
$cell.removeClass('o_invisible_modifier');
return $cell.addClass('o_hidden');
diff --git a/addons/survey/static/src/scss/survey_question_views.scss b/addons/survey/static/src/scss/survey_question_views.scss
new file mode 100644
index 00000000000..02dd9fa0d6c
--- /dev/null
+++ b/addons/survey/static/src/scss/survey_question_views.scss
@@ -0,0 +1,26 @@
+.o_survey_question_view_form {
+
+ .o_preview_questions {
+ border: 3px solid #C9C6C6;
+ width: auto;
+ padding: 10px 20px 10px;
+ margin-top: 15px;
+ color: #C9C6C6;
+ }
+
+ .o_preview_questions .o_datetime {
+ margin-bottom: 5px;
+ }
+
+ .o_preview_questions .o_matrix_head {
+ border-bottom: 1px solid #D8D7D7;
+ }
+
+ .o_preview_questions .o_matrix_row {
+ border-top: 1px solid #D8D7D7;
+ }
+
+ .o_preview_questions_choice {
+ line-height: 1rem;
+ }
+}
diff --git a/addons/survey/tests/test_certification_flow.py b/addons/survey/tests/test_certification_flow.py
index 367dff9211a..2a19cd81d5a 100644
--- a/addons/survey/tests/test_certification_flow.py
+++ b/addons/survey/tests/test_certification_flow.py
@@ -85,7 +85,7 @@ class TestCertificationFlow(common.TestSurveyCommon, HttpCase):
# Employee opens start page
response = self._access_start(certification)
- self.assertResponse(response, 200, [certification.title, 'Time limit for this survey', '10 minutes'])
+ self.assertResponse(response, 200, [certification.title, 'Time limit for this certification', '10 minutes'])
# -> this should have generated a new user_input with a token
user_inputs = self.env['survey.user_input'].search([('survey_id', '=', certification.id)])
diff --git a/addons/survey/tests/test_survey_security.py b/addons/survey/tests/test_survey_security.py
index 5ef21df853a..c783f62f4d4 100644
--- a/addons/survey/tests/test_survey_security.py
+++ b/addons/survey/tests/test_survey_security.py
@@ -256,19 +256,19 @@ class TestAccess(common.TestSurveyCommon):
# Create: own survey only
answer_own = self.env['survey.user_input'].create({'survey_id': survey_own.id})
- answer_line_own = self.env['survey.user_input.line'].create({'question_id': question_own.id, 'answer_type': 'numerical_box', 'value_numerical_box': 3, 'user_input_id': answer_own.id})
+ with self.assertRaises(AccessError):
+ self.env['survey.user_input.line'].create({'question_id': question_own.id, 'answer_type': 'numerical_box', 'value_numerical_box': 3, 'user_input_id': answer_own.id})
# Read: always
answers = self.env['survey.user_input'].search([('survey_id', 'in', [survey_own.id, self.survey.id])])
self.assertEqual(answers, answer_own | self.answer_0)
answer_lines = self.env['survey.user_input.line'].search([('survey_id', 'in', [survey_own.id, self.survey.id])])
- self.assertEqual(answer_lines, answer_line_own | self.answer_0_0 | self.answer_0_1)
+ self.assertEqual(answer_lines, self.answer_0_0 | self.answer_0_1)
self.env['survey.user_input'].browse(answer_own.ids).read(['state'])
self.env['survey.user_input'].browse(self.answer_0.ids).read(['state'])
- self.env['survey.user_input.line'].browse(answer_line_own.ids).read(['value_numerical_box'])
self.env['survey.user_input.line'].browse(self.answer_0_0.ids).read(['value_numerical_box'])
# Create: own survey only (moved after read because DB not correctly rollbacked with assertRaises)
diff --git a/addons/survey/views/res_partner_views.xml b/addons/survey/views/res_partner_views.xml
index 604e5853b3f..7d0a8b76cc9 100644
--- a/addons/survey/views/res_partner_views.xml
+++ b/addons/survey/views/res_partner_views.xml
@@ -4,7 +4,7 @@
Certifications Succeeded
survey.user_input
- tree
+ tree,form
{'search_default_scoring_success': 1}
diff --git a/addons/survey/views/survey_menus.xml b/addons/survey/views/survey_menus.xml
index f16ae85b94c..e464e3b7e86 100644
--- a/addons/survey/views/survey_menus.xml
+++ b/addons/survey/views/survey_menus.xml
@@ -9,14 +9,9 @@
web_icon="survey,static/description/icon.png"/>
-
-
diff --git a/addons/survey/views/survey_question_views.xml b/addons/survey/views/survey_question_views.xml
index 234f8b7b618..39d0ef36bab 100644
--- a/addons/survey/views/survey_question_views.xml
+++ b/addons/survey/views/survey_question_views.xml
@@ -6,7 +6,7 @@
Form view for survey question
survey.question
-