diff --git a/survey_conditional_questions/__manifest__.py b/survey_conditional_questions/__manifest__.py
index 2f5394c..b9e36ae 100644
--- a/survey_conditional_questions/__manifest__.py
+++ b/survey_conditional_questions/__manifest__.py
@@ -1,36 +1,19 @@
-##############################################################################
-#
-# Copyright (C) 2015 ADHOC SA (http://www.adhoc.com.ar)
-# All Rights Reserved.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
+# © 2015 ADHOC SA (http://www.adhoc.com.ar)
+# © 2020 Opener B.V. (https://opener.amsterdam)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Survey Conditional Questions',
- 'version': '11.0.1.1.0',
+ 'version': '11.0.1.2.0',
'category': 'Warehouse Management',
'sequence': 14,
'summary': '',
- 'author': 'ADHOC SA',
+ 'author': 'ADHOC SA, Opener B.V.',
'website': 'www.adhoc.com.ar',
'license': 'AGPL-3',
'images': [
],
'depends': [
'survey',
- 'website'
],
'data': [
'views/survey_question_views.xml',
diff --git a/survey_conditional_questions/controllers/main.py b/survey_conditional_questions/controllers/main.py
index 320525e..f66114e 100644
--- a/survey_conditional_questions/controllers/main.py
+++ b/survey_conditional_questions/controllers/main.py
@@ -3,6 +3,7 @@
# directory
##############################################################################
+import json
import logging
from odoo.addons.survey.controllers.main import WebsiteSurvey
from odoo import http
@@ -14,62 +15,29 @@
class SurveyConditional(WebsiteSurvey):
- # TODO deberiamos heredar esto correctamente
- @http.route()
- def fill_survey(self, survey, token, prev=None, **post):
- '''Display and validates a survey'''
- Survey = request.env['survey.survey']
- UserInput = request.env['survey.user_input']
-
- # Controls if the survey can be displayed
- errpage = self._check_bad_cases(survey)
- if errpage:
- return errpage
-
- # Load the user_input
- user_input = UserInput.sudo().search([('token', '=', token)], limit=1)
- if not user_input: # Invalid token
- return request.render("website.403")
-
- # Do not display expired survey (even if some pages have already been
- # displayed -- There's a time for everything!)
- errpage = self._check_deadline(user_input)
- if errpage:
- return errpage
-
- # Select the right page
- if user_input.state == 'new': # First page
- page, page_nr, last = Survey.next_page(
- user_input, 0, go_back=False)
- data = {'survey': survey, 'page': page,
- 'page_nr': page_nr, 'token': user_input.token}
- data['hide_question_ids'] = UserInput.get_list_questions(
- survey, user_input)
- if last:
- data.update({'last': True})
- return request.render('survey.survey', data)
- elif user_input.state == 'done': # Display success message
- return request.render(
- 'survey.sfinished',
- {'survey': survey, 'token': token, 'user_input': user_input})
- elif user_input.state == 'skip':
- flag = (True if prev and prev == 'prev' else False)
- page, page_nr, last = Survey.next_page(
- user_input, user_input.last_displayed_page_id.id, go_back=flag)
-
- # special case if you click "previous" from the last page,
- # then leave the survey, then reopen it from the URL, avoid crash
- if not page:
- page, page_nr, last = Survey.next_page(
- user_input, user_input.last_displayed_page_id.id,
- go_back=True)
-
- data = {'survey': survey, 'page': page,
- 'page_nr': page_nr, 'token': user_input.token}
- if last:
- data.update({'last': True})
- data['hide_question_ids'] = UserInput.get_list_questions(
- survey, user_input)
- return request.render('survey.survey', data)
+ @http.route(
+ ['/survey/hidden//'
+ '/'],
+ type='http', auth='public', website=True)
+ def hidden(self, survey, token, stored, **post):
+ """ Pass the lists of hidden questions and pages to be applied in the
+ Javascript.
+ :param stored: indicate if we can rely on stored answers from a
+ completed survey, or if we have to determine the hidden questions from
+ the answers filled in so far. """
+ ret = {'hidden_pages': [], 'hidden_questions': []}
+ user_input = request.env['survey.user_input'].sudo().search(
+ [('token', '=', token)])
+ if stored:
+ questions = user_input.user_input_line_ids.filtered(
+ 'hidden').mapped('question_id')
else:
- return request.render("website.403")
+ questions = user_input.get_hidden_questions()
+ for question in questions:
+ question_tag = '%s_%s_%s' % (
+ question.survey_id.id, question.page_id.id, question.id)
+ ret['hidden_questions'].append(question_tag)
+ for page in questions.mapped('page_id'):
+ if not page.question_ids - questions:
+ ret['hidden_pages'].append(page.id)
+ return json.dumps(ret)
diff --git a/survey_conditional_questions/i18n/en.po b/survey_conditional_questions/i18n/en.po
index 4da32bb..162ff00 100644
--- a/survey_conditional_questions/i18n/en.po
+++ b/survey_conditional_questions/i18n/en.po
@@ -28,12 +28,12 @@ msgid "Conditional Question"
msgstr "Conditional Question"
#. module: survey_conditional_questions
-#: help:survey.question,question_conditional_id:0
+#: help:survey.question,triggering_question_id:0
msgid "In order to edit this field you should first save the question"
msgstr "In order to edit this field you should first save the question"
#. module: survey_conditional_questions
-#: field:survey.question,question_conditional_id:0
+#: field:survey.question,triggering_question_id:0
msgid "Question"
msgstr "Question"
diff --git a/survey_conditional_questions/i18n/es.po b/survey_conditional_questions/i18n/es.po
index c32c0d0..a9af9bd 100644
--- a/survey_conditional_questions/i18n/es.po
+++ b/survey_conditional_questions/i18n/es.po
@@ -29,12 +29,12 @@ msgid "Conditional Question"
msgstr "Pregunta Condicional"
#. module: survey_conditional_questions
-#: model:ir.model.fields,help:survey_conditional_questions.field_survey_question_question_conditional_id
+#: model:ir.model.fields,help:survey_conditional_questions.field_survey_question_triggering_question_id
msgid "In order to edit this field you should first save the question"
msgstr "Para editar este campo , primero debe guardar la pregunta"
#. module: survey_conditional_questions
-#: model:ir.model.fields,field_description:survey_conditional_questions.field_survey_question_question_conditional_id
+#: model:ir.model.fields,field_description:survey_conditional_questions.field_survey_question_triggering_question_id
msgid "Question"
msgstr "Pregunta"
diff --git a/survey_conditional_questions/i18n/survey_conditional_questions.pot b/survey_conditional_questions/i18n/survey_conditional_questions.pot
index 763b0f6..4de84ac 100644
--- a/survey_conditional_questions/i18n/survey_conditional_questions.pot
+++ b/survey_conditional_questions/i18n/survey_conditional_questions.pot
@@ -26,12 +26,12 @@ msgid "Conditional Question"
msgstr ""
#. module: survey_conditional_questions
-#: help:survey.question,question_conditional_id:0
+#: help:survey.question,triggering_question_id:0
msgid "In order to edit this field you should first save the question"
msgstr ""
#. module: survey_conditional_questions
-#: field:survey.question,question_conditional_id:0
+#: field:survey.question,triggering_question_id:0
msgid "Question"
msgstr ""
diff --git a/survey_conditional_questions/migrations/11.0.1.2.0/pre-migrate.py b/survey_conditional_questions/migrations/11.0.1.2.0/pre-migrate.py
new file mode 100644
index 0000000..f85fb1e
--- /dev/null
+++ b/survey_conditional_questions/migrations/11.0.1.2.0/pre-migrate.py
@@ -0,0 +1,37 @@
+import logging
+
+
+def migrate(cr, version):
+ logger = logging.getLogger(
+ 'survey_conditional_questions.migrations.11.0.1.3.0')
+
+ def rename(old, new):
+ """ Rename column if the old column exists and the new one does not """
+ cr.execute(
+ """ SELECT EXISTS(
+ SELECT *
+ FROM information_schema.columns
+ WHERE table_name='survey_question'
+ AND column_name=%s) """, (new,))
+ if cr.fetchone()[0]:
+ logger.info('Column %s already exists', new)
+ return
+ cr.execute(
+ """ SELECT EXISTS(
+ SELECT *
+ FROM information_schema.columns
+ WHERE table_name='survey_question'
+ AND column_name=%s) """, (old,))
+ if not cr.fetchone()[0]:
+ logger.info('Column %s does not exist', old)
+ return
+ logger.info('Renaming column %s to %s', new, old)
+ cr.execute(
+ """ ALTER TABLE survey_question
+ RENAME COLUMN %s to %s """ % (old, new))
+
+ for old, new in [
+ ('conditional', 'is_conditional'),
+ ('question_conditional_id', 'triggering_question_id'),
+ ('answer_id', 'triggering_answer_id')]:
+ rename(old, new)
diff --git a/survey_conditional_questions/models/__init__.py b/survey_conditional_questions/models/__init__.py
index c18232d..0186976 100644
--- a/survey_conditional_questions/models/__init__.py
+++ b/survey_conditional_questions/models/__init__.py
@@ -2,5 +2,7 @@
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
+from . import survey_survey
from . import survey_question
from . import survey_user_input
+from . import survey_user_input_line
diff --git a/survey_conditional_questions/models/survey_question.py b/survey_conditional_questions/models/survey_question.py
index 5aef5b9..80acbda 100644
--- a/survey_conditional_questions/models/survey_question.py
+++ b/survey_conditional_questions/models/survey_question.py
@@ -3,29 +3,25 @@
# directory
##############################################################################
from odoo import api, fields, models
-import logging
-
-_logger = logging.getLogger(__name__)
class SurveyQuestion(models.Model):
_inherit = 'survey.question'
- conditional = fields.Boolean(
+ is_conditional = fields.Boolean(
'Conditional Question',
copy=False,
# we add copy = false to avoid wrong link on survey copy,
# should be improoved
)
- question_conditional_id = fields.Many2one(
+ triggering_question_id = fields.Many2one(
'survey.question',
'Question',
copy=False,
- domain="[('survey_id', '=', survey_id)]",
help="In order to edit this field you should"
" first save the question"
)
- answer_id = fields.Many2one(
+ triggering_answer_id = fields.Many2one(
'survey.label',
'Answer',
copy=False,
@@ -33,26 +29,15 @@ class SurveyQuestion(models.Model):
@api.multi
def validate_question(self, post, answer_tag):
- ''' Validate question, depending on question
- type and parameters '''
+ """ Skip validation of hidden questions """
self.ensure_one()
- try:
- checker = getattr(self, 'validate_' + self.type)
- except AttributeError:
- _logger.warning(
- checker.type +
- ": This type of question has no validation method")
- return {}
- else:
- # TODO deberiamos emprolijar esto
- if not self.question_conditional_id:
- return checker(post, answer_tag)
+ if self.triggering_question_id:
input_answer_ids = self.env['survey.user_input_line'].search(
[('user_input_id.token', '=', post.get('token')),
- ('question_id', '=', self.question_conditional_id.id)])
+ ('question_id', '=', self.triggering_question_id.id)])
for answers in input_answer_ids:
value_suggested = answers.value_suggested
- if self.conditional and self.answer_id != value_suggested:
+ if (self.is_conditional and
+ self.triggering_answer_id != value_suggested):
return {}
- else:
- return checker(post, answer_tag)
+ return super(SurveyQuestion, self).validate_question(post, answer_tag)
diff --git a/survey_conditional_questions/models/survey_survey.py b/survey_conditional_questions/models/survey_survey.py
new file mode 100644
index 0000000..8e3fca7
--- /dev/null
+++ b/survey_conditional_questions/models/survey_survey.py
@@ -0,0 +1,28 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+from odoo import api, models
+
+
+class SurveySurvey(models.Model):
+ _inherit = 'survey.survey'
+
+ @api.model
+ def next_page(self, user_input, page_id, go_back=False):
+ """ Skip pages that only have hidden questions on them,
+ except if its the last page or the first page (in which case there
+ is a configuration error in the survey). """
+ questions_to_hide = user_input.get_hidden_questions()
+ res = super(SurveySurvey, self).next_page(
+ user_input, page_id, go_back=go_back)
+ page, index, last = res
+ if page and not (page.question_ids - questions_to_hide):
+ if (not go_back and not last) or (go_back and index):
+ # Mark every question on this hidden page as hidden.
+ for question in page.question_ids:
+ self.env['survey.user_input_line'].update_hidden(
+ user_input, question)
+ return self.next_page(
+ user_input, page.id, go_back=go_back)
+ return res
diff --git a/survey_conditional_questions/models/survey_user_input.py b/survey_conditional_questions/models/survey_user_input.py
index a651b43..0666762 100644
--- a/survey_conditional_questions/models/survey_user_input.py
+++ b/survey_conditional_questions/models/survey_user_input.py
@@ -9,17 +9,17 @@ class SurveyUserInput(models.Model):
_inherit = 'survey.user_input'
@api.model
- def get_list_questions(self, survey, user_input):
- obj_questions = self.env['survey.question']
- questions_to_hide = []
- question_ids = obj_questions.search(
- [('survey_id', '=', survey.id)])
- for question in question_ids.filtered('conditional'):
- for question2 in question_ids.filtered(
- lambda x: x == question.question_conditional_id):
- input_answer_ids = user_input.user_input_line_ids.filtered(
+ def get_hidden_questions(self):
+ """ Return the questions that should be hidden based on the current
+ user input """
+ questions_to_hide = self.env['survey.question']
+ questions = self.survey_id.mapped('page_ids.question_ids')
+ for question in questions.filtered('is_conditional'):
+ for question2 in questions.filtered(
+ lambda x: x == question.triggering_question_id):
+ input_answer_ids = self.user_input_line_ids.filtered(
lambda x: x.question_id == question2)
- for answers in input_answer_ids.filtered(
- lambda x: x.value_suggested != question.answer_id):
- questions_to_hide.append(question.id)
+ if question.triggering_answer_id not in (
+ input_answer_ids.mapped('value_suggested')):
+ questions_to_hide += question
return questions_to_hide
diff --git a/survey_conditional_questions/models/survey_user_input_line.py b/survey_conditional_questions/models/survey_user_input_line.py
new file mode 100644
index 0000000..3bbf48b
--- /dev/null
+++ b/survey_conditional_questions/models/survey_user_input_line.py
@@ -0,0 +1,58 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+from odoo import api, fields, models
+
+
+class SurveyUserInputLine(models.Model):
+ _inherit = 'survey.user_input_line'
+
+ hidden = fields.Boolean(
+ help=('Indicate whether this input\'s question was hidden on '
+ 'condition of earlier questions in the survey.'))
+
+ @api.model
+ def update_hidden(self, user_input, question, hidden=True):
+ """ If hidden, delete all preexisting values and replace by a dummy
+ one marked as hidden. If not hidden, delete any preexisting value
+ marked as hidden. """
+ domain = [
+ ('user_input_id', '=', user_input.id),
+ ('survey_id', '=', question.survey_id.id),
+ ('question_id', '=', question.id)
+ ]
+ if not hidden:
+ # Only select hidden values
+ domain.append(('hidden', '=', True))
+ existing = self.search(domain)
+ if not hidden:
+ if existing:
+ # Remove the hidden value
+ existing.unlink()
+ return
+ if existing:
+ if len(existing) == 1 and existing.hidden:
+ # Nothing to do
+ return existing
+ # Else, wipe all values
+ existing.unlink()
+ return self.create({
+ 'user_input_id': user_input.id,
+ 'question_id': question.id,
+ 'survey_id': question.survey_id.id,
+ 'skipped': True,
+ 'hidden': True,
+ })
+
+ @api.model
+ def save_lines(self, user_input_id, question, post, answer_tag):
+ """ Inject value for 'hidden' in context to be picked up in write and
+ create methods """
+ user_input = self.env['survey.user_input'].browse(user_input_id)
+ hidden = question in user_input.get_hidden_questions()
+ self.update_hidden(user_input, question, hidden=hidden)
+ if hidden:
+ return True
+ return super(SurveyUserInputLine, self).save_lines(
+ user_input_id, question, post, answer_tag)
diff --git a/survey_conditional_questions/static/src/js/survey_conditional_questions.js b/survey_conditional_questions/static/src/js/survey_conditional_questions.js
new file mode 100644
index 0000000..e9cc29e
--- /dev/null
+++ b/survey_conditional_questions/static/src/js/survey_conditional_questions.js
@@ -0,0 +1,30 @@
+odoo.define('survey.conditional_question', function (require) {
+'use strict';
+
+ require('survey.survey');
+ var the_form = $('.js_surveyform');
+
+ function hide_conditional_questions(){
+ // Hide the marked questions and pages
+ var hidden_controller = the_form.attr("data-hidden");
+ if (! _.isUndefined(hidden_controller)) {
+ var hidden_def = $.ajax(hidden_controller, {dataType: "json"}).done(
+ function(json_data){
+ // For each of these, hide the question label and the answer
+ _.each(json_data.hidden_questions, function(key){
+ the_form.find(".js_question-wrapper[id=" + key + "]").css("display", "none");
+ });
+ _.each(json_data.hidden_pages, function(key){
+ var div = the_form.find("h1[data-oe-id=" + key + "][data-oe-model='survey.page']").parent();
+ div.css("display", "none");
+ // Also hide the adjacent hr tag
+ div.prev().css("display", "none");
+ });
+ });
+ }
+ }
+
+ if(the_form.length) {
+ hide_conditional_questions();
+ }
+});
diff --git a/survey_conditional_questions/tests/__init__.py b/survey_conditional_questions/tests/__init__.py
new file mode 100644
index 0000000..5767f87
--- /dev/null
+++ b/survey_conditional_questions/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_survey_conditional_questions
diff --git a/survey_conditional_questions/tests/test_survey_conditional_questions.py b/survey_conditional_questions/tests/test_survey_conditional_questions.py
new file mode 100644
index 0000000..5233f39
--- /dev/null
+++ b/survey_conditional_questions/tests/test_survey_conditional_questions.py
@@ -0,0 +1,56 @@
+from odoo.tests.common import TransactionCase
+
+
+class TestSurveyConditionalQuestions(TransactionCase):
+ def test_survey_conditional_questions(self):
+ label = self.env.ref('survey.choice_1_1_1')
+ question = label.question_id
+ survey = question.survey_id
+
+ conditional_question = survey.page_ids[1].question_ids[0]
+ conditional_values = {
+ 'is_conditional': True,
+ 'triggering_question_id': question.id,
+ 'triggering_answer_id': label.id,
+ }
+ conditional_question.write(conditional_values)
+ user_input = self.env['survey.user_input'].create({
+ 'survey_id': survey.id,
+ 'partner_id': self.env.user.partner_id.id,
+ })
+
+ # Conditional question is hidden when original question not answered
+ self.assertIn(
+ conditional_question, user_input.get_hidden_questions())
+ input_line = self.env['survey.user_input_line'].create({
+ 'answer_type': 'suggestion',
+ 'question_id': question.id,
+ 'skipped': False,
+ 'user_input_id': user_input.id,
+ 'value_suggested': label.id,
+ })
+
+ # Conditional question is not hidden if the desired answer was given
+ self.assertNotIn(
+ conditional_question, user_input.get_hidden_questions())
+
+ # Conditional question is hidden for any other answer
+ input_line.value_suggested = self.env.ref('survey.choice_1_1_2')
+ self.assertIn(conditional_question, user_input.get_hidden_questions())
+
+ # Next page returns the next page
+ self.assertEqual(
+ survey.next_page(user_input, survey.page_ids[0].id)[0],
+ survey.page_ids[1])
+
+ # Hide all the questions on the page
+ survey.page_ids[1].question_ids.write(conditional_values)
+ # Next page skips the next page
+ self.assertEqual(
+ survey.next_page(user_input, survey.page_ids[0].id)[0],
+ survey.page_ids[2])
+ # In both directions
+ self.assertEqual(
+ survey.next_page(
+ user_input, survey.page_ids[2].id, go_back=True)[0],
+ survey.page_ids[0])
diff --git a/survey_conditional_questions/views/survey_question_qweb.xml b/survey_conditional_questions/views/survey_question_qweb.xml
index f4adaa0..324eb47 100644
--- a/survey_conditional_questions/views/survey_question_qweb.xml
+++ b/survey_conditional_questions/views/survey_question_qweb.xml
@@ -1,8 +1,18 @@
-
-
- question.id not in hide_question_ids
+
+
+
+
+
+
+
+ '/survey/hidden/%s/%s/0' % (slug(survey), token)
+
+
+
+
+ '/survey/hidden/%s/%s/1' % (slug(survey), token)
diff --git a/survey_conditional_questions/views/survey_question_views.xml b/survey_conditional_questions/views/survey_question_views.xml
index 3488b86..ebfc0d1 100644
--- a/survey_conditional_questions/views/survey_question_views.xml
+++ b/survey_conditional_questions/views/survey_question_views.xml
@@ -5,12 +5,35 @@
survey.question
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Form view for survey page
+ survey.page
+
+
+
+
+
+
+ {'default_page_id': active_id, 'default_survey_id': survey_id}