[IMP-REF] survey : refactor questions naming, validation and save flow
This commit simplifies the answer_tag and question name by removing the survey_id (uses now only question id) for the most simple cases Factorise the save and validate survey answers to avoid duplicate code Review posted submit data : - process all questions by question type (instead of using form data (key,value) that needed key parsing and was un-typed) - regroup answers by questions and adapt all validation and save flow - remove post data in save and validate question methods and uses directly the answer(s) - remove useless input names The tests have been adapted consequently Task ID : 1930132 PR #32419
This commit is contained in:
@@ -292,7 +292,7 @@ class Survey(http.Controller):
|
||||
This will take into account the validation errors and store the answers to the questions.
|
||||
If the time limit is reached, errors will be skipped, answers will be ignored and
|
||||
survey state will be forced to 'done'"""
|
||||
# Validation
|
||||
# Survey Validation
|
||||
access_data = self._get_access_data(survey_token, answer_token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return {
|
||||
@@ -304,33 +304,39 @@ class Survey(http.Controller):
|
||||
page_id=post.get('page_id'),
|
||||
question_id=post.get('question_id'))
|
||||
|
||||
errors = {}
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s" % (survey_sudo.id, question.id)
|
||||
errors.update(question.validate_question(post, answer_tag))
|
||||
|
||||
if errors:
|
||||
return {
|
||||
'error': 'validation',
|
||||
'fields': errors,
|
||||
}
|
||||
|
||||
if not answer_sudo.test_entry and not survey_sudo._has_attempts_left(answer_sudo.partner_id, answer_sudo.email, answer_sudo.invite_token):
|
||||
# prevent cheating with users creating multiple 'user_input' before their last attempt
|
||||
return {}
|
||||
|
||||
# Submitting survey
|
||||
if not answer_sudo.is_time_limit_reached:
|
||||
# Prepare answers and comment by question
|
||||
prepared_questions = {}
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s" % (survey_sudo.id, question.id)
|
||||
request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, post, answer_tag)
|
||||
answer_full = post.get(str(question.id))
|
||||
answer_without_comment, comment = self._extract_comment_from_answers(question, answer_full)
|
||||
prepared_questions[question.id] = {'answer': answer_without_comment, 'comment': comment}
|
||||
|
||||
# Questions Validation
|
||||
errors = {}
|
||||
for question in questions:
|
||||
answer = prepared_questions[question.id]['answer']
|
||||
comment = prepared_questions[question.id]['comment']
|
||||
errors.update(question.validate_question(answer, comment))
|
||||
if errors:
|
||||
return {'error': 'validation', 'fields': errors}
|
||||
|
||||
# Submitting questions
|
||||
for question in questions:
|
||||
answer = prepared_questions[question.id]['answer']
|
||||
comment = prepared_questions[question.id]['comment']
|
||||
request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, answer, comment)
|
||||
|
||||
if answer_sudo.is_time_limit_reached or survey_sudo.questions_layout == 'one_page':
|
||||
answer_sudo._mark_done()
|
||||
elif 'previous_page_id' in post:
|
||||
# Go back to specific page using the breadcrumb. Lines are saved and survey continues
|
||||
return '/survey/fill/%s/%s?previous_page_id=%s' % (survey_sudo.access_token, answer_token, post['previous_page_id'])
|
||||
elif 'button_submit' in post:
|
||||
else:
|
||||
next_page, unused = request.env['survey.survey'].next_page_or_question(answer_sudo, page_or_question_id)
|
||||
vals = {'last_displayed_page_id': page_or_question_id}
|
||||
|
||||
@@ -579,3 +585,42 @@ class Survey(http.Controller):
|
||||
{"text": "Unanswered", "count": answer_perf['skipped']}
|
||||
])
|
||||
return values
|
||||
|
||||
def _extract_comment_from_answers(self, question, answers):
|
||||
"""
|
||||
As answer is a custom structure depending of the question type
|
||||
that can contain question answers but also comment that need to be extracted
|
||||
before validating and saving answers.
|
||||
If multiple answers, they are listed in an array, except for matrix where answers are structured differently.
|
||||
See input and output for more info on data structures.
|
||||
:param question: survey.question
|
||||
:param answers: {str} answer (for simple questions)
|
||||
or {list} [answer_1,...,answer_n] (for questions with multiple answers)
|
||||
or {list} [answer_1, {'comment': comment}] (for questions with simple answers and with comment)
|
||||
or {list} [answer_1,...,answer_n, {'comment': comment}] (for questions with multiple answers with comment)
|
||||
or {list} [{sub_question_1: [answer_1,...,answer_n]}, {sub_question_2: [...]}] (for matrix without comment)
|
||||
or {list} [{sub_question_1: [answer_1,...,answer_n]}, {sub_question_2: [...]}, {'comment': comment}] (for matrix with comment)
|
||||
:return: answers_no_comment : {str} answer for simple choice (or text and number) question types
|
||||
or {list} [answer_1,...,answer_n] for multiple choices question types except matrix
|
||||
or {list} [{sub_question_1: [answer_1,...,answer_n]}, {sub_question_2: [...]}] for matrix
|
||||
comment : {str} extracted comment for the given question
|
||||
"""
|
||||
comment = None
|
||||
answers_no_comment = []
|
||||
if answers:
|
||||
if question.question_type == 'matrix':
|
||||
if 'comment' in answers:
|
||||
comment = answers['comment'].strip()
|
||||
answers.pop('comment')
|
||||
answers_no_comment = answers
|
||||
else:
|
||||
if not isinstance(answers, list):
|
||||
answers = [answers]
|
||||
for answer in answers:
|
||||
if 'comment' in answer:
|
||||
comment = answer['comment'].strip()
|
||||
else:
|
||||
answers_no_comment.append(answer)
|
||||
if len(answers_no_comment) == 1:
|
||||
answers_no_comment = answers_no_comment[0]
|
||||
return answers_no_comment, comment
|
||||
|
||||
@@ -68,11 +68,6 @@
|
||||
<field name="sequence">4</field>
|
||||
<field name="value">Once a year</field>
|
||||
</record>
|
||||
<record model="survey.label" id="survey_feedback_p1_q3_sug5">
|
||||
<field name="question_id" ref="survey_feedback_p1_q3"/>
|
||||
<field name="sequence">5</field>
|
||||
<field name="value">Other (answer in comment)</field>
|
||||
</record>
|
||||
<record model="survey.question" id="survey_feedback_p1_q4">
|
||||
<field name="survey_id" ref="survey_feedback" />
|
||||
<field name="sequence">5</field>
|
||||
@@ -140,11 +135,6 @@
|
||||
<field name="sequence">8</field>
|
||||
<field name="value">Poor quality</field>
|
||||
</record>
|
||||
<record model="survey.label" id="survey_feedback_p2_q1_sug9">
|
||||
<field name="question_id" ref="survey_feedback_p2_q1"/>
|
||||
<field name="sequence">9</field>
|
||||
<field name="value">Other</field>
|
||||
</record>
|
||||
<record model="survey.question" id="survey_feedback_p2_q2">
|
||||
<field name="survey_id" ref="survey_feedback" />
|
||||
<field name="sequence">8</field>
|
||||
|
||||
@@ -152,173 +152,102 @@ class SurveyQuestion(models.Model):
|
||||
self.question_type = False
|
||||
|
||||
# Validation methods
|
||||
def validate_question(self, answer, comment=None):
|
||||
""" Validate question, depending on question type and parameters
|
||||
for simple choice, text, date and number, answer is simply the answer of the question.
|
||||
For other multiple choices questions, answer is a list of answers (the selected choices
|
||||
or a list of selected answers per question -for matrix type-):
|
||||
- Simple answer : answer = 'example' or 2 or question_answer_id or 2019/10/10
|
||||
- Multiple choice : answer = [question_answer_id1, question_answer_id2, question_answer_id3]
|
||||
- Matrix: answer = { 'rowId1' : [colId1, colId2,...], 'rowId2' : [colId1, colId3, ...] }
|
||||
|
||||
def validate_question(self, post, answer_tag):
|
||||
""" Validate question, depending on question type and parameters """
|
||||
return dict {question.id (int): error (str)} -> empty dict if no validation error.
|
||||
"""
|
||||
self.ensure_one()
|
||||
try:
|
||||
checker = getattr(self, 'validate_' + self.question_type)
|
||||
except AttributeError:
|
||||
_logger.warning(self.question_type + ": This type of question has no validation method")
|
||||
return {}
|
||||
else:
|
||||
return checker(post, answer_tag)
|
||||
|
||||
def validate_free_text(self, post, answer_tag):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
if isinstance(answer, str):
|
||||
answer = answer.strip()
|
||||
# Empty answer to mandatory question
|
||||
if self.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
return errors
|
||||
if self.constr_mandatory and not answer and self.question_type not in ['simple_choice', 'multiple_choice']:
|
||||
return {self.id: self.constr_error_msg}
|
||||
|
||||
def validate_textbox(self, post, answer_tag):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if self.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
# because in choices question types, comment can count as answer
|
||||
if answer or self.question_type in ['simple_choice', 'multiple_choice']:
|
||||
if self.question_type == 'textbox':
|
||||
return self._validate_textbox(answer)
|
||||
elif self.question_type == 'numerical_box':
|
||||
return self._validate_numerical_box(answer)
|
||||
elif self.question_type in ['date', 'datetime']:
|
||||
return self._validate_date(answer)
|
||||
elif self.question_type in ['simple_choice', 'multiple_choice']:
|
||||
return self._validate_choice(answer, comment)
|
||||
elif self.question_type == 'matrix':
|
||||
return self._validate_matrix(answer)
|
||||
return {}
|
||||
|
||||
def _validate_textbox(self, answer):
|
||||
# Email format validation
|
||||
# Note: this validation is very basic:
|
||||
# all the strings of the form
|
||||
# <something>@<anything>.<extension>
|
||||
# will be accepted
|
||||
if answer and self.validation_email:
|
||||
# all the strings of the form "<something>@<anything>.<extension>" will be accepted
|
||||
if self.validation_email:
|
||||
if not email_validator.match(answer):
|
||||
errors.update({answer_tag: _('This answer must be an email address')})
|
||||
return {self.id: _('This answer must be an email address')}
|
||||
|
||||
# Answer validation (if properly defined)
|
||||
# Length of the answer must be in a range
|
||||
if answer and self.validation_required:
|
||||
if self.validation_required:
|
||||
if not (self.validation_length_min <= len(answer) <= self.validation_length_max):
|
||||
errors.update({answer_tag: self.validation_error_msg})
|
||||
return errors
|
||||
return {self.id: self.validation_error_msg}
|
||||
return {}
|
||||
|
||||
def validate_numerical_box(self, post, answer_tag):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if self.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
# Checks if user input is a number
|
||||
if answer:
|
||||
try:
|
||||
floatanswer = float(answer)
|
||||
except ValueError:
|
||||
errors.update({answer_tag: _('This is not a number')})
|
||||
# Answer validation (if properly defined)
|
||||
if answer and self.validation_required:
|
||||
def _validate_numerical_box(self, answer):
|
||||
try:
|
||||
floatanswer = float(answer)
|
||||
except ValueError:
|
||||
return {self.id: _('This is not a number')}
|
||||
|
||||
if self.validation_required:
|
||||
# Answer is not in the right range
|
||||
with tools.ignore(Exception):
|
||||
floatanswer = float(answer) # check that it is a float has been done hereunder
|
||||
if not (self.validation_min_float_value <= floatanswer <= self.validation_max_float_value):
|
||||
errors.update({answer_tag: self.validation_error_msg})
|
||||
return errors
|
||||
return {self.id: self.validation_error_msg}
|
||||
return {}
|
||||
|
||||
def date_validation(self, date_type, post, answer_tag, min_value, max_value):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
if date_type not in ('date', 'datetime'):
|
||||
raise ValueError("Unexpected date type value")
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if self.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
def _validate_date(self, answer):
|
||||
isDatetime = self.question_type == 'datetime'
|
||||
# Checks if user input is a date
|
||||
if answer:
|
||||
try:
|
||||
if date_type == 'datetime':
|
||||
dateanswer = fields.Datetime.from_string(answer)
|
||||
else:
|
||||
dateanswer = fields.Date.from_string(answer)
|
||||
except ValueError:
|
||||
errors.update({answer_tag: _('This is not a date')})
|
||||
return errors
|
||||
# Answer validation (if properly defined)
|
||||
if answer and self.validation_required:
|
||||
# Answer is not in the right range
|
||||
try:
|
||||
if date_type == 'datetime':
|
||||
date_from_string = fields.Datetime.from_string
|
||||
else:
|
||||
date_from_string = fields.Date.from_string
|
||||
dateanswer = date_from_string(answer)
|
||||
min_date = date_from_string(min_value)
|
||||
max_date = date_from_string(max_value)
|
||||
|
||||
if min_date and max_date and not (min_date <= dateanswer <= max_date):
|
||||
# If Minimum and Maximum Date are entered
|
||||
errors.update({answer_tag: self.validation_error_msg})
|
||||
elif min_date and not min_date <= dateanswer:
|
||||
# If only Minimum Date is entered and not Define Maximum Date
|
||||
errors.update({answer_tag: self.validation_error_msg})
|
||||
elif max_date and not dateanswer <= max_date:
|
||||
# If only Maximum Date is entered and not Define Minimum Date
|
||||
errors.update({answer_tag: self.validation_error_msg})
|
||||
except ValueError: # check that it is a date has been done hereunder
|
||||
pass
|
||||
return errors
|
||||
|
||||
def validate_date(self, post, answer_tag):
|
||||
return self.date_validation('date', post, answer_tag, self.validation_min_date, self.validation_max_date)
|
||||
|
||||
def validate_datetime(self, post, answer_tag):
|
||||
return self.date_validation('datetime', post, answer_tag, self.validation_min_datetime, self.validation_max_datetime)
|
||||
|
||||
def validate_simple_choice(self, post, answer_tag):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
if self.comments_allowed:
|
||||
comment_tag = "%s_%s" % (answer_tag, 'comment')
|
||||
# Empty answer to mandatory self
|
||||
if self.constr_mandatory and answer_tag not in post:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
if self.constr_mandatory and answer_tag in post and not post[answer_tag].strip():
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
# Answer is a comment and is empty
|
||||
if self.constr_mandatory and answer_tag in post and post[answer_tag] == "-1" and self.comment_count_as_answer and comment_tag in post and not post[comment_tag].strip():
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
return errors
|
||||
|
||||
def validate_multiple_choice(self, post, answer_tag):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
if self.constr_mandatory:
|
||||
answer_candidates = dict_keys_startswith(post, answer_tag)
|
||||
comment_flag = answer_candidates.pop(("%s_%s" % (answer_tag, -1)), None)
|
||||
if self.comments_allowed:
|
||||
comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
|
||||
# Preventing answers with blank value
|
||||
if all(not answer.strip() for answer in answer_candidates.values()) and answer_candidates:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
# There is no answer neither comments (if comments count as answer)
|
||||
if not answer_candidates and self.comment_count_as_answer and (not comment_flag or not comment_answer):
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
# There is no answer at all
|
||||
if not answer_candidates and not self.comment_count_as_answer:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
return errors
|
||||
|
||||
def validate_matrix(self, post, answer_tag):
|
||||
self.ensure_one()
|
||||
errors = {}
|
||||
if self.constr_mandatory:
|
||||
lines_number = len(self.labels_ids_2)
|
||||
answer_candidates = dict_keys_startswith(post, answer_tag)
|
||||
answer_candidates.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
|
||||
# Number of lines that have been answered
|
||||
if self.matrix_subtype == 'simple':
|
||||
answer_number = len(answer_candidates)
|
||||
elif self.matrix_subtype == 'multiple':
|
||||
answer_number = len({sk.rsplit('_', 1)[0] for sk in answer_candidates})
|
||||
try:
|
||||
dateanswer = fields.Datetime.from_string(answer) if isDatetime else fields.Date.from_string(answer)
|
||||
except ValueError:
|
||||
return {self.id: _('This is not a date')}
|
||||
if self.validation_required:
|
||||
# Check if answer is in the right range
|
||||
if isDatetime:
|
||||
min_date = fields.Datetime.from_string(self.validation_min_datetime)
|
||||
max_date = fields.Datetime.from_string(self.validation_max_datetime)
|
||||
dateanswer = fields.Datetime.from_string(answer)
|
||||
else:
|
||||
raise RuntimeError("Invalid matrix subtype")
|
||||
# Validate that each line has been answered
|
||||
if answer_number != lines_number:
|
||||
errors.update({answer_tag: self.constr_error_msg})
|
||||
return errors
|
||||
min_date = fields.Date.from_string(self.validation_min_date)
|
||||
max_date = fields.Date.from_string(self.validation_max_date)
|
||||
dateanswer = fields.Date.from_string(answer)
|
||||
|
||||
if (min_date and max_date and not (min_date <= dateanswer <= max_date))\
|
||||
or (min_date and not min_date <= dateanswer)\
|
||||
or (max_date and not dateanswer <= max_date):
|
||||
return {self.id: self.validation_error_msg}
|
||||
return {}
|
||||
|
||||
def _validate_choice(self, answer, comment):
|
||||
# Empty comment
|
||||
if self.constr_mandatory \
|
||||
and not answer \
|
||||
and not (self.comments_allowed and self.comment_count_as_answer and comment):
|
||||
return {self.id: self.constr_error_msg}
|
||||
return {}
|
||||
|
||||
def _validate_matrix(self, answers):
|
||||
# Validate that each line has been answered
|
||||
if self.constr_mandatory and len(self.labels_ids_2) != len(answers):
|
||||
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):
|
||||
|
||||
@@ -267,240 +267,96 @@ class SurveyUserInputLine(models.Model):
|
||||
vals.update({'answer_score': self.env['survey.label'].browse(int(value_suggested)).answer_score})
|
||||
return super(SurveyUserInputLine, self).write(vals)
|
||||
|
||||
def _get_save_line_values(self, answer, answer_type):
|
||||
if not answer or (isinstance(answer, str) and not answer.strip()):
|
||||
return {'answer_type': None, 'skipped': True}
|
||||
if answer_type == 'suggestion':
|
||||
return {'answer_type': answer_type, 'value_suggested': answer}
|
||||
value = float(answer) if answer_type == 'number' else answer
|
||||
return {'answer_type': answer_type, 'value_' + answer_type: value}
|
||||
|
||||
@api.model
|
||||
def save_lines(self, user_input_id, question, post, answer_tag):
|
||||
def save_lines(self, user_input_id, question, answer, comment=None):
|
||||
""" Save answers to questions, depending on question type
|
||||
|
||||
If an answer already exists for question and user_input_id, it will be
|
||||
overwritten (in order to maintain data consistency).
|
||||
overwritten (or deleted for 'choice' questions) (in order to maintain data consistency).
|
||||
"""
|
||||
try:
|
||||
saver = getattr(self, 'save_line_' + question.question_type)
|
||||
except AttributeError:
|
||||
_logger.error(question.question_type + ": This type of question has no saving function")
|
||||
return False
|
||||
else:
|
||||
saver(user_input_id, question, post, answer_tag)
|
||||
|
||||
@api.model
|
||||
def save_line_free_text(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False,
|
||||
}
|
||||
if answer_tag in post and post[answer_tag].strip():
|
||||
vals.update({'answer_type': 'free_text', 'value_free_text': post[answer_tag]})
|
||||
else:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
old_uil = self.search([
|
||||
old_answers = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
if old_uil:
|
||||
old_uil.write(vals)
|
||||
|
||||
if question.question_type in ['textbox', 'free_text', 'numerical_box', 'date', 'datetime']:
|
||||
self._save_line_simple_answer(vals, old_answers, question, answer)
|
||||
elif question.question_type in ['simple_choice', 'multiple_choice']:
|
||||
self._save_line_choice(vals, old_answers, question, answer, comment)
|
||||
elif question.question_type == 'matrix':
|
||||
self._save_line_matrix(vals, old_answers, answer, comment)
|
||||
else:
|
||||
old_uil.create(vals)
|
||||
return True
|
||||
raise AttributeError(question.question_type + ": This type of question has no saving function")
|
||||
|
||||
@api.model
|
||||
def save_line_textbox(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
if answer_tag in post and post[answer_tag].strip():
|
||||
vals.update({'answer_type': 'text', 'value_text': post[answer_tag]})
|
||||
def _save_line_simple_answer(self, vals, old_answers, question, answer):
|
||||
answer_type = question.question_type
|
||||
if question.question_type == 'textbox':
|
||||
answer_type = 'text'
|
||||
elif question.question_type == 'numerical_box':
|
||||
answer_type = 'number'
|
||||
|
||||
vals.update(self._get_save_line_values(answer, answer_type))
|
||||
if old_answers:
|
||||
old_answers.write(vals)
|
||||
else:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
if old_uil:
|
||||
old_uil.write(vals)
|
||||
else:
|
||||
old_uil.create(vals)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def save_line_numerical_box(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
if answer_tag in post and post[answer_tag].strip():
|
||||
vals.update({'answer_type': 'number', 'value_number': float(post[answer_tag])})
|
||||
else:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
if old_uil:
|
||||
old_uil.write(vals)
|
||||
else:
|
||||
old_uil.create(vals)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def save_line_date(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
if answer_tag in post and post[answer_tag].strip():
|
||||
vals.update({'answer_type': 'date', 'value_date': post[answer_tag]})
|
||||
else:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
if old_uil:
|
||||
old_uil.write(vals)
|
||||
else:
|
||||
old_uil.create(vals)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def save_line_datetime(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
if answer_tag in post and post[answer_tag].strip():
|
||||
vals.update({'answer_type': 'datetime', 'value_datetime': post[answer_tag]})
|
||||
else:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
if old_uil:
|
||||
old_uil.write(vals)
|
||||
else:
|
||||
old_uil.create(vals)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def save_line_simple_choice(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
old_uil.sudo().unlink()
|
||||
|
||||
if answer_tag in post and post[answer_tag].strip():
|
||||
vals.update({'answer_type': 'suggestion', 'value_suggested': int(post[answer_tag])})
|
||||
else:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
|
||||
# '-1' indicates 'comment count as an answer' so do not need to record it
|
||||
if post.get(answer_tag) and post.get(answer_tag) != '-1':
|
||||
self.create(vals)
|
||||
|
||||
comment_answer = post.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
|
||||
if comment_answer:
|
||||
vals.update({'answer_type': 'text', 'value_text': comment_answer, 'skipped': False, 'value_suggested': False})
|
||||
self.create(vals)
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def save_line_multiple_choice(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
old_uil.sudo().unlink()
|
||||
|
||||
ca_dict = dict_keys_startswith(post, answer_tag + '_')
|
||||
comment_answer = ca_dict.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
|
||||
if len(ca_dict) > 0:
|
||||
for key in ca_dict:
|
||||
# '-1' indicates 'comment count as an answer' so do not need to record it
|
||||
if key != ('%s_%s' % (answer_tag, '-1')):
|
||||
val = ca_dict[key]
|
||||
vals.update({'answer_type': 'suggestion', 'value_suggested': bool(val) and int(val)})
|
||||
self.create(vals)
|
||||
if comment_answer:
|
||||
vals.update({'answer_type': 'text', 'value_text': comment_answer, 'value_suggested': False})
|
||||
self.create(vals)
|
||||
if not ca_dict and not comment_answer:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
self.create(vals)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def save_line_matrix(self, user_input_id, question, post, answer_tag):
|
||||
vals = {
|
||||
'user_input_id': user_input_id,
|
||||
'question_id': question.id,
|
||||
'survey_id': question.survey_id.id,
|
||||
'skipped': False
|
||||
}
|
||||
old_uil = self.search([
|
||||
('user_input_id', '=', user_input_id),
|
||||
('survey_id', '=', question.survey_id.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
old_uil.sudo().unlink()
|
||||
def _save_line_choice(self, vals, old_answers, question, answers, comment):
|
||||
if not (isinstance(answers, list)):
|
||||
answers = [answers]
|
||||
|
||||
no_answers = True
|
||||
ca_dict = dict_keys_startswith(post, answer_tag + '_')
|
||||
vals_list = []
|
||||
if question.question_type == 'simple_choice':
|
||||
if not (question.comment_count_as_answer and question.comments_allowed and comment):
|
||||
for answer in answers:
|
||||
vals.update(self._get_save_line_values(answer, 'suggestion'))
|
||||
vals_list.append(vals.copy())
|
||||
elif question.question_type == 'multiple_choice':
|
||||
for answer in answers:
|
||||
vals.update(self._get_save_line_values(answer, 'suggestion'))
|
||||
vals_list.append(vals.copy())
|
||||
|
||||
comment_answer = ca_dict.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
|
||||
if comment_answer:
|
||||
vals.update({'answer_type': 'text', 'value_text': comment_answer})
|
||||
self.create(vals)
|
||||
no_answers = False
|
||||
if comment:
|
||||
vals.update({'answer_type': 'text', 'value_text': comment, 'skipped': False, 'value_suggested': False})
|
||||
vals_list.append(vals.copy())
|
||||
|
||||
if question.matrix_subtype == 'simple':
|
||||
for row in question.labels_ids_2:
|
||||
a_tag = "%s_%s" % (answer_tag, row.id)
|
||||
if a_tag in ca_dict:
|
||||
no_answers = False
|
||||
vals.update({'answer_type': 'suggestion', 'value_suggested': ca_dict[a_tag], 'value_suggested_row': row.id})
|
||||
self.create(vals)
|
||||
old_answers.sudo().unlink()
|
||||
self.create(vals_list)
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _save_line_matrix(self, vals, old_answers, answers, comment):
|
||||
vals_list = []
|
||||
|
||||
for row_key, row_answer in answers.items():
|
||||
for answer in row_answer:
|
||||
vals.update({'answer_type': 'suggestion', 'value_suggested': answer, 'value_suggested_row': row_key})
|
||||
vals_list.append(vals.copy())
|
||||
|
||||
if comment:
|
||||
vals.update({'answer_type': 'text', 'value_text': comment, 'skipped': False, 'value_suggested': False})
|
||||
vals_list.append(vals.copy())
|
||||
|
||||
old_answers.sudo().unlink()
|
||||
self.create(vals_list)
|
||||
|
||||
elif question.matrix_subtype == 'multiple':
|
||||
for col in question.labels_ids:
|
||||
for row in question.labels_ids_2:
|
||||
a_tag = "%s_%s_%s" % (answer_tag, row.id, col.id)
|
||||
if a_tag in ca_dict:
|
||||
no_answers = False
|
||||
vals.update({'answer_type': 'suggestion', 'value_suggested': col.id, 'value_suggested_row': row.id})
|
||||
self.create(vals)
|
||||
if no_answers:
|
||||
vals.update({'answer_type': None, 'skipped': True})
|
||||
self.create(vals)
|
||||
return True
|
||||
|
||||
@@ -10,7 +10,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
selector: '.o_survey_form',
|
||||
events: {
|
||||
'change .o_survey_form_choice_item': '_onChangeChoiceItem',
|
||||
'click button[name="button_submit"]': '_onSubmit',
|
||||
'click button[type="submit"]': '_onSubmit',
|
||||
'click .o_survey_header .breadcrumb-item a': '_onBreadcrumbClick',
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
this.surveyTimerWidget = new publicWidget.registry.SurveyTimerWidget(this);
|
||||
this.surveyTimerWidget.attachTo($timer);
|
||||
this.surveyTimerWidget.on('time_up', this, function (ev) {
|
||||
self.$el.find('button[name="button_submit"]').click();
|
||||
self.$el.find('button[type="submit"]').click();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,9 +101,11 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
|
||||
_onSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
var $target = $(event.currentTarget);
|
||||
var submitValue = $target.val();
|
||||
this._submitForm({'button_submit': submitValue})
|
||||
if ($(event.currentTarget).val() === 'previous') {
|
||||
this._submitForm({'previous_id': $(event.currentTarget).data('previousId')});
|
||||
} else {
|
||||
this._submitForm({});
|
||||
}
|
||||
},
|
||||
|
||||
_submitForm: function (params) {
|
||||
@@ -111,9 +113,6 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
var $form = this.$('form');
|
||||
var formData = new FormData($form[0]);
|
||||
|
||||
this.$('div.o_survey_form_date').each(function () {
|
||||
self._updateDateForSubmit($(this), formData);
|
||||
});
|
||||
this._prepareSubmitValues(formData, params);
|
||||
|
||||
this._resetErrors();
|
||||
@@ -149,64 +148,113 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
}
|
||||
},
|
||||
|
||||
// INIT FIELDS
|
||||
// PREPARE SUBMIT TOOLS
|
||||
// -------------------------------------------------------------------------
|
||||
/*
|
||||
* Convert the client side date format into server side date format before submit
|
||||
* For each type of question, extract the answer from inputs or textarea (comment or answer)
|
||||
*
|
||||
*
|
||||
* @private
|
||||
* @param {Event} event
|
||||
*/
|
||||
_updateDateForSubmit: function (dateGroup, formData) {
|
||||
var input = $(dateGroup).find('input');
|
||||
var dateValue = input.val();
|
||||
var questionType = $(input).closest('.o_survey_form_date').data('questiontype');
|
||||
if (dateValue) {
|
||||
var momentDate = questionType === 'datetime' ? field_utils.parse.datetime(dateValue, null, {timezone: true}) : field_utils.parse.date(dateValue);
|
||||
var newDate = momentDate ? momentDate.toJSON() : '';
|
||||
formData.set(input.attr('name'), newDate);
|
||||
input.val(newDate);
|
||||
}
|
||||
},
|
||||
|
||||
// TOOLS
|
||||
// -------------------------------------------------------------------------
|
||||
_prepareSubmitValues: function (formData, params) {
|
||||
var self = this;
|
||||
// Get all context params -- TODO : Use formData instead (test if input with no name are in formData)
|
||||
formData.forEach(function(value, key){
|
||||
if (value !== -1) {
|
||||
// Handles Comment
|
||||
if (key.indexOf('_comment') !== -1){
|
||||
key = key.split('_comment')[0];
|
||||
value = {'comment': value};
|
||||
}
|
||||
// Handles Matrix - Matrix answer_tag are composed like : 'questionId_rowId_colId'
|
||||
// and are the only ones with this structure.
|
||||
var splitKey = key.split('_');
|
||||
if (splitKey.length === 3 && splitKey[2] === value) {
|
||||
params = self._prepareSubmitMatrix(params, splitKey, value);
|
||||
}
|
||||
// Handles the rest
|
||||
else {
|
||||
params = self._prepareSubmitOther(params, key, value);
|
||||
}
|
||||
switch (key) {
|
||||
case 'csrf_token':
|
||||
case 'token':
|
||||
case 'page_id':
|
||||
case 'question_id':
|
||||
params[key] = value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Get all question answers by question type
|
||||
this.$('[data-question-type]').each(function () {
|
||||
switch ($(this).data('questionType')) {
|
||||
case 'free_text':
|
||||
case 'textbox':
|
||||
case 'numerical_box':
|
||||
params[this.name] = this.value;
|
||||
break;
|
||||
case 'date':
|
||||
params = self._prepareSubmitDates(params, this.name, this.value, false);
|
||||
break;
|
||||
case 'datetime':
|
||||
params = self._prepareSubmitDates(params, this.name, this.value, true);
|
||||
break;
|
||||
case 'simple_choice_dropdown':
|
||||
params = self._prepareSubmitChoices(params, $(this), $(this).data('name'), 'option:selected');
|
||||
break;
|
||||
case 'simple_choice_radio':
|
||||
case 'multiple_choice':
|
||||
params = self._prepareSubmitChoices(params, $(this), $(this).data('name'), 'input:checked');
|
||||
break;
|
||||
case 'matrix':
|
||||
params = self._prepareSubmitAnswersMatrix(params, $(this));
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare date answer before submitting form.
|
||||
* Convert date value from client current timezone to UTC Date to correspond to the server format.
|
||||
* return params = { 'dateQuestionId' : '2019-05-23', 'datetimeQuestionId' : '2019-05-23 14:05:12' }
|
||||
*/
|
||||
_prepareSubmitDates: function(params, questionId, value, isDateTime) {
|
||||
var momentDate = isDateTime ? field_utils.parse.datetime(value, null, {timezone: true}) : field_utils.parse.date(value);
|
||||
var formattedDate = momentDate ? momentDate.toJSON() : '';
|
||||
params[questionId] = formattedDate;
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare choice answer before submitting form.
|
||||
* If the answer is not the 'comment selection' (=Other), calls the _prepareSubmitAnswer method to add the answer to the params
|
||||
* If there is a comment linked to that question, calls the _prepareSubmitComment method to add the comment to the params
|
||||
*/
|
||||
_prepareSubmitChoices: function(params, $parent, questionId, selector) {
|
||||
var self = this;
|
||||
$parent.find(selector).each(function () {
|
||||
if (this.value != '-1') {
|
||||
params = self._prepareSubmitAnswer(params, questionId, this.value);
|
||||
}
|
||||
});
|
||||
params = self._prepareSubmitComment(params, $parent, questionId, false);
|
||||
return params;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Prepare matrix answers before submitting form.
|
||||
* This method adds matrix answers one by one and add comment if any to a params key,value like :
|
||||
* params = { 'matrixQuestionId' : {'rowId1': [colId1, colId2,...], 'rowId2': [colId1, colId3, ...], 'comment': comment }}
|
||||
*/
|
||||
_prepareSubmitAnswersMatrix: function(params, $matrixDiv) {
|
||||
var self = this;
|
||||
$matrixDiv.find('input:checked').each(function () {
|
||||
params = self._prepareSubmitAnswerMatrix(params, $matrixDiv.data('name'), $(this).data('rowId'), this.value);
|
||||
});
|
||||
params = self._prepareSubmitComment(params, $matrixDiv, $matrixDiv.data('name'), true);
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare answer before submitting form if question type is matrix.
|
||||
* This method regroups answers by question and by row to make a object like :
|
||||
* This method regroups answers by question and by row to make an object like :
|
||||
* params = { 'matrixQuestionId' : { 'rowId1' : [colId1, colId2,...], 'rowId2' : [colId1, colId3, ...] } }
|
||||
*/
|
||||
_prepareSubmitMatrix: function(params, splitKey, value) {
|
||||
var key = splitKey[0];
|
||||
var rowId = splitKey[1];
|
||||
var colId = splitKey[2];
|
||||
value = key in params ? params[key] : {};
|
||||
_prepareSubmitAnswerMatrix: function(params, questionId, rowId, colId) {
|
||||
var value = questionId in params ? params[questionId] : {};
|
||||
if (rowId in value) {
|
||||
value[rowId].push(colId);
|
||||
} else {
|
||||
value[rowId] = [colId];
|
||||
}
|
||||
params[key] = value;
|
||||
params[questionId] = value;
|
||||
return params;
|
||||
},
|
||||
|
||||
@@ -216,37 +264,43 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
* Lonely answer are directly assigned to questionId. Multiple answers are regrouped in an array:
|
||||
* params = { 'questionId1' : lonelyAnswer, 'questionId2' : [multipleAnswer1, multipleAnswer2, ...] }
|
||||
*/
|
||||
_prepareSubmitOther: function(params, key, value) {
|
||||
if (key in params) {
|
||||
if (params[key].constructor === Array) {
|
||||
params[key].push(value);
|
||||
_prepareSubmitAnswer: function(params, questionId, value) {
|
||||
if (questionId in params) {
|
||||
if (params[questionId].constructor === Array) {
|
||||
params[questionId].push(value);
|
||||
} else {
|
||||
params[key] = [params[key], value];
|
||||
params[questionId] = [params[questionId], value];
|
||||
}
|
||||
} else {
|
||||
params[key] = value;
|
||||
params[questionId] = value;
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert date value in client current timezone (if already answered)
|
||||
* Prepare comment before submitting form.
|
||||
* This method extract the comment, encapsulate it in a dict and calls the _prepareSubmitAnswer methods
|
||||
* with the new value. At the end, the result looks like :
|
||||
* params = { 'questionId1' : {'comment': commentValue}, 'questionId2' : [multipleAnswer1, {'comment': commentValue}, ...] }
|
||||
*/
|
||||
_formatDateValue: function ($dateGroup) {
|
||||
var input = $dateGroup.find('input');
|
||||
var dateValue = input.val();
|
||||
if (dateValue !== '') {
|
||||
var momentDate = field_utils.parse.date(dateValue);
|
||||
if ($dateGroup.data('questiontype') === 'datetime') {
|
||||
dateValue = field_utils.format.datetime(momentDate, null, {timezone: true});
|
||||
} else {
|
||||
dateValue = field_utils.format.date(momentDate, null, {timezone: true});
|
||||
_prepareSubmitComment: function(params, $parent, questionId, isMatrix) {
|
||||
var self = this;
|
||||
$parent.find('textarea').each(function () {
|
||||
if (this.value) {
|
||||
var value = {'comment': this.value};
|
||||
if (isMatrix) {
|
||||
params = self._prepareSubmitAnswerMatrix(params, questionId, this.name, value);
|
||||
} else {
|
||||
params = self._prepareSubmitAnswer(params, questionId, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
input.val(dateValue);
|
||||
return dateValue
|
||||
});
|
||||
return params
|
||||
},
|
||||
|
||||
// INIT FIELDS TOOLS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Initialize datetimepicker in correct format and with constraints
|
||||
*/
|
||||
@@ -259,7 +313,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
var maxDate = maxDateData ? this._formatDateTime(maxDateData) : moment().add(200, "y");
|
||||
|
||||
var datetimepickerFormat = time.getLangDateFormat()
|
||||
if ($dateGroup.data('questiontype') === 'datetime') {
|
||||
if ($dateGroup.find('input').data('questionType') === 'datetime') {
|
||||
datetimepickerFormat = time.getLangDatetimeFormat()
|
||||
} else {
|
||||
// Include min and max date in selectable values
|
||||
@@ -296,6 +350,9 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
return field_utils.format.datetime(moment(datetimeValue), null, {timezone: true});
|
||||
},
|
||||
|
||||
// ERRORS TOOLS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_scrollToError: function ($target) {
|
||||
var scrollLocation = $target.offset().top;
|
||||
var navbarHeight = $('.o_main_navbar').height();
|
||||
|
||||
@@ -62,7 +62,7 @@ var failSteps = [{ // Page-1
|
||||
}
|
||||
}, {
|
||||
content: "Finish Survey",
|
||||
trigger: 'button[value="finish"]',
|
||||
trigger: 'button[type="submit"]',
|
||||
}];
|
||||
|
||||
var retrySteps = [{
|
||||
|
||||
@@ -62,7 +62,7 @@ tour.register('test_certification_success', {
|
||||
}
|
||||
}, {
|
||||
content: "Finish Survey",
|
||||
trigger: 'button[value="finish"]',
|
||||
trigger: 'button[type="submit"]',
|
||||
}, {
|
||||
content: "Thank you",
|
||||
trigger: 'h1:contains("Thank you!")',
|
||||
|
||||
@@ -31,7 +31,7 @@ tour.register('test_survey', {
|
||||
trigger: 'div.js_question-wrapper:contains("How many times did you order products on our website") input',
|
||||
run: 'text 12',
|
||||
}, {
|
||||
content: 'Click on Next Page',
|
||||
content: 'Submit and go to Next Page',
|
||||
trigger: 'button[value="next"]',
|
||||
},
|
||||
// Page-2
|
||||
@@ -61,7 +61,7 @@ tour.register('test_survey', {
|
||||
trigger: 'div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea',
|
||||
run: 'text This is great. Really.',
|
||||
}, {
|
||||
content: 'Click Submit',
|
||||
content: 'Click Submit and finish the survey',
|
||||
trigger: 'button[value="finish"]',
|
||||
},
|
||||
// Final page
|
||||
|
||||
@@ -55,23 +55,16 @@ tour.register('test_survey_prefill', {
|
||||
trigger: 'div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea',
|
||||
run: 'text Is the prefill working?',
|
||||
}, {
|
||||
content: 'Click on Previous Page',
|
||||
trigger: 'button[value="previous"]',
|
||||
// Go back to previous page
|
||||
content: 'Click on the previous page name in the breadcrumb',
|
||||
trigger: 'ol.breadcrumb a:first',
|
||||
}, {
|
||||
trigger: 'div.js_question-wrapper:contains("How many times did you order products on our website ?") input',
|
||||
run: function () {
|
||||
// wait for prefill
|
||||
// booo ugly (but will be removed when ajax prefill is removed)
|
||||
var maxAttempts = 0;
|
||||
var checkPrefillLoaded = setInterval(function () {
|
||||
if ($('div.js_question-wrapper:contains("How many times did you order products on our website ?") input').val() === '42.0') {
|
||||
$('.o_survey_title').addClass('prefilled');
|
||||
clearInterval(checkPrefillLoaded);
|
||||
} else if (maxAttempts >= 50) {
|
||||
clearInterval(checkPrefillLoaded);
|
||||
}
|
||||
maxAttempts++;
|
||||
}, 100); // check every 100ms
|
||||
var $inputQ3 = $('div.js_question-wrapper:contains("How many times did you order products on our website ?") input');
|
||||
if ($inputQ3.val() === '42.0') {
|
||||
$('.o_survey_title').addClass('prefilled');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: '.o_survey_title.prefilled',
|
||||
@@ -107,18 +100,10 @@ tour.register('test_survey_prefill', {
|
||||
}, {
|
||||
trigger: 'div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea',
|
||||
run: function () {
|
||||
// wait for prefill
|
||||
// booo ugly (but will be removed when ajax prefill is removed)
|
||||
var maxAttempts = 0;
|
||||
var checkPrefillLoaded = setInterval(function () {
|
||||
if ($('div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea').val() === "Is the prefill working?") {
|
||||
$('.o_survey_title').addClass('prefilled2');
|
||||
clearInterval(checkPrefillLoaded);
|
||||
} else if (maxAttempts >= 50) {
|
||||
clearInterval(checkPrefillLoaded);
|
||||
}
|
||||
maxAttempts++;
|
||||
}, 100); // check every 100ms
|
||||
var $inputQ3 = $('div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea');
|
||||
if ($inputQ3.val() === "Is the prefill working?") {
|
||||
$('.o_survey_title').addClass('prefilled2');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: '.o_survey_title.prefilled2',
|
||||
|
||||
@@ -137,7 +137,10 @@ class TestSurveyCommon(common.SavepointCase):
|
||||
else:
|
||||
[value] = user_input['value']
|
||||
answer_fname = self._type_match[question.question_type][1]
|
||||
self.assertEqual(getattr(answer_lines, answer_fname), value)
|
||||
if question.question_type == 'numerical_box':
|
||||
self.assertEqual(getattr(answer_lines, answer_fname), float(value))
|
||||
else:
|
||||
self.assertEqual(getattr(answer_lines, answer_fname), value)
|
||||
|
||||
def assertResponse(self, response, status_code, text_bits=None):
|
||||
self.assertEqual(response.status_code, status_code)
|
||||
@@ -222,3 +225,20 @@ class TestSurveyCommon(common.SavepointCase):
|
||||
def _find_csrf_token(self, text):
|
||||
csrf_token_re = re.compile("(input.+csrf_token.+value=\")([_a-zA-Z0-9]{51})", re.MULTILINE)
|
||||
return csrf_token_re.search(text).groups()[1]
|
||||
|
||||
def _prepare_post_data(self, question, answers, post_data):
|
||||
values = answers if isinstance(answers, list) else [answers]
|
||||
if question.question_type == 'multiple_choice':
|
||||
for value in values:
|
||||
value = str(value)
|
||||
if question.id in post_data:
|
||||
if isinstance(post_data[question.id], list):
|
||||
post_data[question.id].append(value)
|
||||
else:
|
||||
post_data[question.id] = [post_data[question.id], value]
|
||||
else:
|
||||
post_data[question.id] = value
|
||||
else:
|
||||
[values] = values
|
||||
post_data[question.id] = str(values)
|
||||
return post_data
|
||||
|
||||
@@ -24,14 +24,7 @@ class TestCertificationFlow(common.TestSurveyCommon, HttpCase):
|
||||
def _format_submission_data(self, question, answer, additional_post_data):
|
||||
post_data = {}
|
||||
post_data['question_id'] = question.id
|
||||
if question.question_type == 'multiple_choice':
|
||||
values = answer
|
||||
for value in values:
|
||||
key = "%s_%s_%s" % (question.survey_id.id, question.id, value)
|
||||
post_data[key] = value
|
||||
else:
|
||||
key = "%s_%s" % (question.survey_id.id, question.id)
|
||||
post_data[key] = answer
|
||||
post_data.update(self._prepare_post_data(question, answer, post_data))
|
||||
post_data.update(**additional_post_data)
|
||||
return post_data
|
||||
|
||||
|
||||
@@ -29,10 +29,9 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
||||
kwargs['labels_2'] = [{'value': 'Row0'}, {'value': 'Row1'}]
|
||||
question = self._add_question(self.page_0, 'Q0', question_type, **kwargs)
|
||||
|
||||
answer_tag = '%s_%s' % (self.survey.id, question.id)
|
||||
self.assertDictEqual(
|
||||
question.validate_question({answer_tag: ''}, answer_tag),
|
||||
{answer_tag: 'TestError'}
|
||||
question.validate_question(''),
|
||||
{question.id: 'TestError'}
|
||||
)
|
||||
|
||||
@users('survey_manager')
|
||||
@@ -40,25 +39,24 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
||||
question = self._add_question(
|
||||
self.page_0, 'Q0', 'date', validation_required=True,
|
||||
validation_min_date='2015-03-20', validation_max_date='2015-03-25', validation_error_msg='ValidationError')
|
||||
answer_tag = '%s_%s' % (self.survey.id, question.id)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'Is Alfred an answer ?'}, answer_tag),
|
||||
{answer_tag: _('This is not a date')}
|
||||
question.validate_question('Is Alfred an answer ?'),
|
||||
{question.id: _('This is not a date')}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: '2015-03-19'}, answer_tag),
|
||||
{answer_tag: 'ValidationError'}
|
||||
question.validate_question('2015-03-19'),
|
||||
{question.id: 'ValidationError'}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: '2015-03-26'}, answer_tag),
|
||||
{answer_tag: 'ValidationError'}
|
||||
question.validate_question('2015-03-26'),
|
||||
{question.id: 'ValidationError'}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: '2015-03-25'}, answer_tag),
|
||||
question.validate_question('2015-03-25'),
|
||||
{}
|
||||
)
|
||||
|
||||
@@ -67,40 +65,38 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
||||
question = self._add_question(
|
||||
self.page_0, 'Q0', 'numerical_box', validation_required=True,
|
||||
validation_min_float_value=2.2, validation_max_float_value=3.3, validation_error_msg='ValidationError')
|
||||
answer_tag = '%s_%s' % (self.survey.id, question.id)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'Is Alfred an answer ?'}, answer_tag),
|
||||
{answer_tag: _('This is not a number')}
|
||||
question.validate_question('Is Alfred an answer ?'),
|
||||
{question.id: _('This is not a number')}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: '2.0'}, answer_tag),
|
||||
{answer_tag: 'ValidationError'}
|
||||
question.validate_question('2.0'),
|
||||
{question.id: 'ValidationError'}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: '4.0'}, answer_tag),
|
||||
{answer_tag: 'ValidationError'}
|
||||
question.validate_question('4.0'),
|
||||
{question.id: 'ValidationError'}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: '2.9'}, answer_tag),
|
||||
question.validate_question('2.9'),
|
||||
{}
|
||||
)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_answer_validation_textbox_email(self):
|
||||
question = self._add_question(self.page_0, 'Q0', 'textbox', validation_email=True)
|
||||
answer_tag = '%s_%s' % (self.survey.id, question.id)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'not an email'}, answer_tag),
|
||||
{answer_tag: _('This answer must be an email address')}
|
||||
question.validate_question('not an email'),
|
||||
{question.id: _('This answer must be an email address')}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'email@example.com'}, answer_tag),
|
||||
question.validate_question('email@example.com'),
|
||||
{}
|
||||
)
|
||||
|
||||
@@ -109,20 +105,19 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
||||
question = self._add_question(
|
||||
self.page_0, 'Q0', 'textbox', validation_required=True,
|
||||
validation_length_min=2, validation_length_max=8, validation_error_msg='ValidationError')
|
||||
answer_tag = '%s_%s' % (self.survey.id, question.id)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'l'}, answer_tag),
|
||||
{answer_tag: 'ValidationError'}
|
||||
question.validate_question('l'),
|
||||
{question.id: 'ValidationError'}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'waytoomuchlonganswer'}, answer_tag),
|
||||
{answer_tag: 'ValidationError'}
|
||||
question.validate_question('waytoomuchlonganswer'),
|
||||
{question.id: 'ValidationError'}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
question.validate_question({answer_tag: 'valid'}, answer_tag),
|
||||
question.validate_question('valid'),
|
||||
{}
|
||||
)
|
||||
|
||||
|
||||
@@ -13,15 +13,7 @@ class TestSurveyFlow(common.TestSurveyCommon, HttpCase):
|
||||
post_data['page_id'] = page.id
|
||||
for question_id, answer_vals in answer_data.items():
|
||||
question = page.question_ids.filtered(lambda q: q.id == question_id)
|
||||
if question.question_type == 'multiple_choice':
|
||||
values = answer_vals['value']
|
||||
for value in values:
|
||||
key = "%s_%s_%s" % (page.survey_id.id, question.id, value)
|
||||
post_data[key] = value
|
||||
else:
|
||||
[value] = answer_vals['value']
|
||||
key = "%s_%s" % (page.survey_id.id, question.id)
|
||||
post_data[key] = value
|
||||
post_data.update(self._prepare_post_data(question, answer_vals['value'], post_data))
|
||||
post_data.update(**additional_post_data)
|
||||
return post_data
|
||||
|
||||
@@ -96,7 +88,7 @@ class TestSurveyFlow(common.TestSurveyCommon, HttpCase):
|
||||
# Customer submit first page answers
|
||||
answer_data = {
|
||||
page0_q0.id: {'value': ['Alfred Poilvache']},
|
||||
page0_q1.id: {'value': [44.0]},
|
||||
page0_q1.id: {'value': ['44.0']},
|
||||
}
|
||||
post_data = self._format_submission_data(page_0, answer_data, {'csrf_token': csrf_token, 'token': answer_token, 'button_submit': 'next'})
|
||||
r = self._access_submit(survey, answer_token, post_data)
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
</t>
|
||||
|
||||
<div class="text-center mt16 mb256">
|
||||
<button type="submit" class="btn btn-primary" name="button_submit" value="finish">Submit Survey</button>
|
||||
<button type="submit" class="btn btn-primary">Submit Survey</button>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -285,8 +285,9 @@
|
||||
<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 t-if="not last" type="submit" class="btn btn-primary" name="button_submit" value="next">Next page</button>
|
||||
<button t-if="last" type="submit" class="btn btn-primary" name="button_submit" value="finish">Submit Survey</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>
|
||||
|
||||
@@ -297,8 +298,9 @@
|
||||
<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 t-if="not last" type="submit" class="btn btn-primary" name="button_submit" value="next">Next question</button>
|
||||
<button t-if="last" type="submit" class="btn btn-primary" name="button_submit" value="finish">Submit Survey</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>
|
||||
@@ -319,9 +321,8 @@
|
||||
</template>
|
||||
|
||||
<template id="question" name="Question">
|
||||
<t t-set="prefix" t-value="'%s_%s' % (survey.id, question.id)" />
|
||||
<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="prefix">
|
||||
<div class="js_question-wrapper" t-att-id="question.id">
|
||||
<h4>
|
||||
<span t-field='question.question' />
|
||||
<span t-if="question.constr_mandatory" class="text-danger">*</span>
|
||||
@@ -341,23 +342,28 @@
|
||||
|
||||
<!-- Question widgets -->
|
||||
<template id="free_text" name="Free text box">
|
||||
<textarea class="form-control" rows="3" t-att-name="prefix"><t t-if="answer_lines" t-esc="answer_lines[0].value_free_text or None"/></textarea>
|
||||
<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">
|
||||
<input type="text" class="form-control" t-att-name="prefix" t-att-value="answer_lines[0].value_text if answer_lines else None"/>
|
||||
<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">
|
||||
<input type="number" step="any" class="form-control" t-att-name="prefix" t-att-value="answer_lines[0].value_number if answer_lines else None"/>
|
||||
<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">
|
||||
<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">
|
||||
<input type="text" class="form-control datetimepicker-input" t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="prefix"
|
||||
t-att-value="format_date(answer_lines[0].value_date) if answer_lines else None"/>
|
||||
<input type="text" class="form-control datetimepicker-input o_survey_question_date"
|
||||
t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="question.id"
|
||||
t-att-value="format_date(answer_lines[0].value_date) if answer_lines else None"
|
||||
t-att-data-question-type="question.question_type"/>
|
||||
<div t-if="not survey_form_readonly" class="input-group-append" t-attf-data-target="#datetimepicker_#{question.id}" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
@@ -367,10 +373,11 @@
|
||||
<template id="datetime" name="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"
|
||||
t-att-data-questiontype="question.question_type">
|
||||
<input type="text" class="form-control datetimepicker-input" t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="prefix"
|
||||
t-att-value="format_datetime(answer_lines[0].value_datetime) if answer_lines else None"/>
|
||||
t-att-data-maxdate="question.validation_max_datetime">
|
||||
<input type="text" class="form-control datetimepicker-input o_survey_question_datetime"
|
||||
t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="question.id"
|
||||
t-att-value="format_datetime(answer_lines[0].value_datetime) if answer_lines else None"
|
||||
t-att-data-question-type="question.question_type"/>
|
||||
<div t-if="not survey_form_readonly" class="input-group-append" t-attf-data-target="#datetimepicker_#{question.id}" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
@@ -380,9 +387,11 @@
|
||||
<template id="simple_choice" name="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 row">
|
||||
<div t-if="question.display_mode == 'dropdown'" class="o_survey_form_choice o_survey_question_simple_choice_dropdown row"
|
||||
t-att-data-name="question.id"
|
||||
data-question-type="simple_choice_dropdown">
|
||||
<div class="col-lg-12">
|
||||
<select class="form-control o_survey_form_choice_item" t-att-name="prefix">
|
||||
<select class="form-control o_survey_form_choice_item">
|
||||
<option disabled="1" selected="1" value=""
|
||||
t-att-selected="not answer_line and (not comment_line or not question.comment_count_as_answer) and 'selected' or None">Choose...</option>
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
@@ -399,73 +408,74 @@
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="col-lg-6">
|
||||
<textarea type="text" class="form-control"
|
||||
t-attf-class="form-control #{'' if comment_line else 'd-none'}"
|
||||
t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"
|
||||
t-att-value="comment_line.value_text if comment_line else ''"/>
|
||||
t-attf-class="form-control #{'' if comment_line else 'd-none'}"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12 mt16">
|
||||
<span t-field="question.comments_message"/>
|
||||
<textarea type="text" class="form-control o_survey_comment" t-att-name="'%s_%s' % (prefix, 'comment')"
|
||||
t-att-value="comment_line.value_text if comment_line else ''"/>
|
||||
<textarea type="text" class="form-control o_survey_comment"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="question.display_mode == 'columns'" class="row o_survey_form_choice">
|
||||
<div t-if="question.display_mode == 'columns'" class="row o_survey_form_choice o_survey_question_simple_choice_radio"
|
||||
t-att-data-name="question.id"
|
||||
data-question-type="simple_choice_radio">
|
||||
<div t-foreach='question.labels_ids' t-as='label' t-attf-class="col-lg-#{question.column_nb}">
|
||||
<label t-att-class="' bg-success ' if quizz_correction and label.answer_score > 0.0 else None">
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.id' class="o_survey_form_choice_item"
|
||||
<input type="radio" t-att-value='label.id' class="o_survey_form_choice_item"
|
||||
t-att-checked="answer_line and answer_line.value_suggested.id == label.id and 'checked' or None"/>
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_comments col-lg-12" >
|
||||
<label>
|
||||
<input type="radio" class="o_survey_form_choice_item o_survey_js_form_other_comment" t-att-name="prefix" value="-1"
|
||||
<input type="radio" class="o_survey_form_choice_item o_survey_js_form_other_comment" value="-1"
|
||||
t-att-checked="comment_line and 'checked' or None"/>
|
||||
<span t-field="question.comments_message" />
|
||||
</label>
|
||||
<textarea type="text" class="form-control" rows="1"
|
||||
t-attf-disabled="#{'' if comment_line else 'disabled'}"
|
||||
t-att-name="'%s_%s' % (prefix, 'comment')"/>
|
||||
<textarea type="text" class="form-control"
|
||||
t-attf-disabled="#{'' if comment_line else 'disabled'}"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12">
|
||||
<span t-field="question.comments_message"/>
|
||||
<textarea type="text" class="form-control o_survey_comment" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"/>
|
||||
<textarea type="text" class="form-control o_survey_comment"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="multiple_choice" name="Multiple choice">
|
||||
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_text)"/>
|
||||
<div class="row o_survey_form_choice">
|
||||
<div class="row o_survey_form_choice o_survey_question_multiple_choice"
|
||||
t-att-data-name="question.id"
|
||||
t-att-data-question-type="question.question_type">
|
||||
<div t-foreach='question.labels_ids' t-as='label' t-attf-class="col-lg-#{question.column_nb}">
|
||||
<t t-set="answer_line" t-value="answer_lines.filtered(lambda line: line.value_suggested == label)"/>
|
||||
<label t-att-class="' bg-success ' if quizz_correction and label.answer_score > 0.0 else None">
|
||||
<input type="checkbox" t-att-name="'%s_%s' % (prefix, label.id)" t-att-value='label.id' class="o_survey_form_choice_item"
|
||||
<input type="checkbox" t-att-value='label.id' class="o_survey_form_choice_item"
|
||||
t-att-checked="'checked' if answer_line else None"/>
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_ck_comments col-lg-12" >
|
||||
<label>
|
||||
<input type="checkbox" t-att-name="'%s_%s' % (prefix, -1)" class="o_survey_form_choice_item o_survey_js_form_other_comment" value="-1"
|
||||
t-att-checked="'checked' if comment_line else None"/>
|
||||
<input type="checkbox" class="o_survey_form_choice_item o_survey_js_form_other_comment" value="-1"
|
||||
t-att-checked="comment_line and 'checked' or None"/>
|
||||
<span t-field="question.comments_message" />
|
||||
</label>
|
||||
<textarea type="text" class="form-control"
|
||||
t-attf-disabled="#{'' if comment_line else 'disabled'}"
|
||||
t-att-name="'%s_%s' % (prefix, 'comment')"
|
||||
t-att-value="comment_line.value_text if comment_line else None"/>
|
||||
t-attf-disabled="#{'' if comment_line else 'disabled'}"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12">
|
||||
<span t-field="question.comments_message"/>
|
||||
<textarea type="text" class="form-control" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"
|
||||
t-att-value="comment_line.value_text if comment_line else None"/>
|
||||
<textarea type="text" class="form-control o_survey_comment"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="matrix" name="Matrix">
|
||||
<table class="table table-hover">
|
||||
<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"
|
||||
t-att-data-question-type="question.question_type">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
@@ -477,17 +487,17 @@
|
||||
<th><span t-field="row_label.value" /></th>
|
||||
<td t-foreach="question.labels_ids" t-as="col_label">
|
||||
<t t-set="answer" t-value="answer_lines.filtered(lambda line: line.value_suggested == col_label and line.value_suggested_row == row_label)"/>
|
||||
<input t-if="question.matrix_subtype == 'simple'" type="radio" t-att-name="'%s_%s' % (prefix, row_label.id)" t-att-value='col_label.id'
|
||||
t-att-checked="'checked' if answer else None"/>
|
||||
<input t-if="question.matrix_subtype == 'multiple'" type="checkbox" t-att-name="'%s_%s_%s' % (prefix, row_label.id, col_label.id)" t-att-value='col_label.id'
|
||||
t-att-checked="'checked' if answer else None"/>
|
||||
<input t-att-type="'checkbox' if question.matrix_subtype == 'multiple' else 'radio'"
|
||||
t-att-name="'%s_%s' % (question_id, row_label.id)" t-att-value='col_label.id'
|
||||
t-att-checked="'checked' if answer else None"
|
||||
t-att-data-row-id="row_label.id"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div t-if='question.comments_allowed'>
|
||||
<span t-field="question.comments_message"/>
|
||||
<textarea class="form-control" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')" />
|
||||
<textarea type="text" class="form-control" t-att-name="'%s_%s' % (question.id, 'comment')"><t t-esc="comment_line.value_text if comment_line else ''"/></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -512,9 +522,8 @@
|
||||
<div t-if="question.description" t-field='question.description' class="oe_no_empty"/>
|
||||
</div>
|
||||
<t t-if="not question.is_page and not answer or (question in answer.question_ids)" >
|
||||
<t t-set="prefix" t-value="'%s_%s' % (survey.id, question.id)" />
|
||||
<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="prefix">
|
||||
<div class="js_question-wrapper" t-att-id="question.id">
|
||||
<h2>
|
||||
<span t-field='question.question' />
|
||||
<span t-if="question.constr_mandatory" class="text-danger">*</span>
|
||||
|
||||
Reference in New Issue
Block a user