From bb3d4634df39dec2f899397628641d96525e5f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 26 Nov 2019 09:57:51 +0000 Subject: [PATCH] [FIX] survey: lint JS files and reorganize route code PURPOSE As new features are about to land in survey, notably live interactions [1] and new survey building [2] performing a pre cleaning is necessary. In this PR we clean survey models by: removing unnecessary fields, cleaning some code and finally renaming models. SPECIFICATIONS This commit lints JS files located in survey as some typos and mistakes were recently introduced. Code related to routes is also slightly reorganized to better understand file main sections. LINKS [0] Related to Task ID 2061901 (survey models cleaning and preparation) [1] Task ID 1972640 (live interactions) [2] Task ID 2119587 (new frontend for building surveys) PR #40765 --- addons/survey/controllers/main.py | 253 ++++++++++--------- addons/survey/static/src/js/survey_form.js | 27 +- addons/survey/static/src/js/survey_result.js | 10 +- 3 files changed, 150 insertions(+), 140 deletions(-) diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py index 753027cfb96..b6147ca937f 100644 --- a/addons/survey/controllers/main.py +++ b/addons/survey/controllers/main.py @@ -185,6 +185,14 @@ class Survey(http.Controller): 'deadline': answer.deadline, } + def _prepare_survey_finished_values(self, survey, answer, token=False): + values = {'survey': survey, 'answer': answer} + if token: + values['token'] = token + if survey.scoring_type != 'no_scoring' and survey.certification: + values['graph_data'] = json.dumps(answer._prepare_statistics()[0]) + return values + # ------------------------------------------------------------ # TAKING SURVEY ROUTES # ------------------------------------------------------------ @@ -348,6 +356,51 @@ class Survey(http.Controller): return '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token) + def _extract_comment_from_answers(self, question, answers): + """ Answers is a custom structure depending of the question type + that can contain question answers but also comments 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: + * question_type: free_text, text_box, numerical_box, date, datetime + answers is a string containing the value + * question_type: simple_choice with no comment + answers is a string containing the value ('question_id_1') + * question_type: simple_choice with comment + ['question_id_1', {'comment': str}] + * question_type: multiple choice + ['question_id_1', 'question_id_2'] + [{'comment': str}] if holds a comment + * question_type: matrix + {'matrix_row_id_1': ['question_id_1', 'question_id_2'], + 'matrix_row_id_2': ['question_id_1', 'question_id_2'] + } + {'comment': str} if holds a comment + :return: tuple( + same structure without comment, + extracted comment for 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 + # ------------------------------------------------------------ # COMPLETED SURVEY ROUTES # ------------------------------------------------------------ @@ -375,65 +428,6 @@ class Survey(http.Controller): 'format_date': lambda date: format_date(request.env, date) }) - @http.route('/survey/results/', type='http', auth='user', website=True) - def survey_report(self, survey, answer_token=None, **post): - '''Display survey Results & Statistics for given survey.''' - current_filters = [] - filter_display_data = [] - - answers = survey.user_input_ids.filtered(lambda answer: answer.state != 'new' and not answer.test_entry) - filter_finish = post.get('finished') == 'true' - if post or filter_finish: - filter_data = self._get_filter_data(post) - current_filters = survey.filter_input_ids(filter_data, filter_finish) - filter_display_data = survey.get_filter_display_data(filter_data) - return request.render('survey.survey_page_statistics', - {'survey': survey, - 'answers': answers, - 'survey_dict': self._prepare_result_dict(survey, current_filters), - 'page_range': self.page_range, - 'current_filters': current_filters, - 'filter_display_data': filter_display_data, - 'filter_finish': filter_finish - }) - # Quick retroengineering of what is injected into the template for now: - # (TODO: flatten and simplify this) - # - # survey: a browse record of the survey - # survey_dict: very messy dict containing all the info to display answers - # {'page_ids': [ - # - # ... - # - # {'page': browse record of the page, - # 'question_ids': [ - # - # ... - # - # {'graph_data': data to be displayed on the graph - # 'input_summary': number of answered, skipped... - # 'prepare_result': { - # answers displayed in the tables - # } - # 'question': browse record of the question_ids - # } - # - # ... - # - # ] - # } - # - # ... - # - # ] - # } - # - # page_range: pager helper function - # current_filters: a list of ids - # filter_display_data: [{'labels': ['a', 'b'], question_text} ... ] - # filter_finish: boolean => only finished surveys or not - # - @http.route(['/survey//get_certification_preview'], type="http", auth="user", methods=['GET'], website=True) def survey_get_certification_preview(self, survey, **kwargs): if not request.env.user.has_group('survey.group_survey_user'): @@ -467,6 +461,85 @@ class Survey(http.Controller): return self._generate_report(succeeded_attempt, download=True) + def _generate_report(self, user_input, download=True): + report = request.env.ref('survey.certification_report').sudo().render_qweb_pdf([user_input.id], data={'report_type': 'pdf'})[0] + + report_content_disposition = content_disposition('Certification.pdf') + if not download: + content_split = report_content_disposition.split(';') + content_split[0] = 'inline' + report_content_disposition = ';'.join(content_split) + + return request.make_response(report, headers=[ + ('Content-Type', 'application/pdf'), + ('Content-Length', len(report)), + ('Content-Disposition', report_content_disposition), + ]) + + # ------------------------------------------------------------ + # REPORTING SURVEY ROUTES + # ------------------------------------------------------------ + + @http.route('/survey/results/', type='http', auth='user', website=True) + def survey_report(self, survey, answer_token=None, **post): + """ Display survey Results & Statistics for given survey. + + Quick retroengineering of what is injected into the template for now: + (TODO: flatten and simplify this) + + survey: a browse record of the survey + survey_dict: very messy dict containing all the info to display answers + {'page_ids': [ + + ... + + {'page': browse record of the page, + 'question_ids': [ + + ... + + {'graph_data': data to be displayed on the graph + 'input_summary': number of answered, skipped... + 'prepare_result': { + answers displayed in the tables + } + 'question': browse record of the question_ids + } + + ... + + ] + } + + ... + + ] + } + + page_range: pager helper function + current_filters: a list of ids + filter_display_data: [{'labels': ['a', 'b'], question_text} ... ] + filter_finish: boolean => only finished surveys or not + """ + current_filters = [] + filter_display_data = [] + + answers = survey.user_input_ids.filtered(lambda answer: answer.state != 'new' and not answer.test_entry) + filter_finish = post.get('finished') == 'true' + if post or filter_finish: + filter_data = self._get_filter_data(post) + current_filters = survey.filter_input_ids(filter_data, filter_finish) + filter_display_data = survey.get_filter_display_data(filter_data) + return request.render('survey.survey_page_statistics', { + 'survey': survey, + 'answers': answers, + 'survey_dict': self._prepare_result_dict(survey, current_filters), + 'page_range': self.page_range, + 'current_filters': current_filters, + 'filter_display_data': filter_display_data, + 'filter_finish': filter_finish + }) + def _prepare_result_dict(self, survey, current_filters=None): """Returns dictionary having values for rendering template""" current_filters = current_filters if current_filters else [] @@ -569,65 +642,3 @@ class Survey(http.Controller): 'success_rate': round((scoring_success_count / total_scoring_success) * 100, 1) if total_scoring_success > 0 else 0, 'graph_data': graph_data } - - def _prepare_survey_finished_values(self, survey, answer, token=False): - values = {'survey': survey, 'answer': answer} - if token: - values['token'] = token - if survey.scoring_type != 'no_scoring' and survey.certification: - values['graph_data'] = json.dumps(answer._prepare_statistics()[0]) - 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 - - def _generate_report(self, user_input, download=True): - report = request.env.ref('survey.certification_report').sudo().render_qweb_pdf([user_input.id], data={'report_type': 'pdf'})[0] - - report_content_disposition = content_disposition('Certification.pdf') - if not download: - content_split = report_content_disposition.split(';') - content_split[0] = 'inline' - report_content_disposition = ';'.join(content_split) - - return request.make_response(report, headers=[ - ('Content-Type', 'application/pdf'), - ('Content-Length', len(report)), - ('Content-Disposition', report_content_disposition), - ]) diff --git a/addons/survey/static/src/js/survey_form.js b/addons/survey/static/src/js/survey_form.js index cae3ab20666..ecb74022d56 100644 --- a/addons/survey/static/src/js/survey_form.js +++ b/addons/survey/static/src/js/survey_form.js @@ -1,7 +1,6 @@ odoo.define('survey.form', function(require) { 'use strict'; -var ajax = require('web.ajax'); var field_utils = require('web.field_utils'); var publicWidget = require('web.public.widget'); var time = require('web.time'); @@ -24,7 +23,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ start: function () { var self = this; return this._super.apply(this, arguments).then(function() { - self.options = self.$target.find('form').data() + self.options = self.$target.find('form').data(); var $timer = $('.o_survey_timer'); if ($timer.length) { var timeLimitMinutes = self.options.timeLimitMinutes; @@ -70,9 +69,9 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ // Handle dropdownlist var $dropdownlist = $choiceItemGroup.find('select'); - var isDropdown = $dropdownlist.length > 0 + var isDropdown = $dropdownlist.length > 0; - var isOtherSelected = (isDropdown && $dropdownlist.val() === $otherItem.val()) || $otherItem.prop('checked') + var isOtherSelected = (isDropdown && $dropdownlist.val() === $otherItem.val()) || $otherItem.prop('checked'); if (isOtherSelected || $commentInput.hasClass('o_survey_comment')) { if (isDropdown) { @@ -100,7 +99,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ */ _onBreadcrumbClick: function (event) { event.preventDefault(); - this._submitForm({'previous_page_id': $(event.currentTarget).data('previousPageId')}) + this._submitForm({'previous_page_id': $(event.currentTarget).data('previousPageId')}); }, _onSubmit: function (event) { @@ -223,7 +222,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ _prepareSubmitChoices: function(params, $parent, questionId, selector) { var self = this; $parent.find(selector).each(function () { - if (this.value != '-1') { + if (this.value !== '-1') { params = self._prepareSubmitAnswer(params, questionId, this.value); } }); @@ -299,7 +298,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ } } }); - return params + return params; }, // INIT FIELDS TOOLS @@ -309,21 +308,21 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ * Initialize datetimepicker in correct format and with constraints */ _initDateTimePicker: function ($dateGroup) { - var disabledDates = [] + var disabledDates = []; - var minDateData = $dateGroup.data('mindate') + var minDateData = $dateGroup.data('mindate'); var minDate = minDateData ? this._formatDateTime(minDateData) : moment({ y: 1900 }); - var maxDateData = $dateGroup.data('maxdate') + var maxDateData = $dateGroup.data('maxdate'); var maxDate = maxDateData ? this._formatDateTime(maxDateData) : moment().add(200, "y"); var datetimepickerFormat = time.getLangDateFormat() if ($dateGroup.find('input').data('questionType') === 'datetime') { - datetimepickerFormat = time.getLangDatetimeFormat() + datetimepickerFormat = time.getLangDatetimeFormat(); } else { // Include min and max date in selectable values maxDate = moment(maxDate).add(1, "d"); minDate = moment(minDate).subtract(1, "d"); - disabledDates = [minDate, maxDate] + disabledDates = [minDate, maxDate]; } $dateGroup.datetimepicker({ @@ -360,8 +359,8 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({ _scrollToError: function ($target) { var scrollLocation = $target.offset().top; var navbarHeight = $('.o_main_navbar').height(); - if (navbarHeight != undefined) { - scrollLocation -= navbarHeight + if (navbarHeight) { + scrollLocation -= navbarHeight; } $('html, body').animate({ scrollTop: scrollLocation diff --git a/addons/survey/static/src/js/survey_result.js b/addons/survey/static/src/js/survey_result.js index d2a6659f10e..3919015af00 100644 --- a/addons/survey/static/src/js/survey_result.js +++ b/addons/survey/static/src/js/survey_result.js @@ -367,8 +367,8 @@ publicWidget.registry.SurveyResultWidget = publicWidget.Widget.extend({ var row_id = cell.data('row_id') | 0; var answer_id = cell.data('answer_id'); - var params = new URLSearchParams(window.location.search) - var filters = params.get('filters') ? params.get('filters') + "|" + row_id + ',' + answer_id : row_id + ',' + answer_id + var params = new URLSearchParams(window.location.search); + var filters = params.get('filters') ? params.get('filters') + "|" + row_id + ',' + answer_id : row_id + ',' + answer_id; params.set('filters', filters); window.location.href = window.location.pathname + '?' + params.toString(); @@ -379,7 +379,7 @@ publicWidget.registry.SurveyResultWidget = publicWidget.Widget.extend({ * @param {Event} ev */ _onClearFilterClick: function (ev) { - var params = new URLSearchParams(window.location.search) + var params = new URLSearchParams(window.location.search); params.delete('filters'); params.delete('finished'); window.location.href = window.location.pathname + '?' + params.toString(); @@ -390,7 +390,7 @@ publicWidget.registry.SurveyResultWidget = publicWidget.Widget.extend({ * @param {Event} ev */ _onFilterAllClick: function (ev) { - var params = new URLSearchParams(window.location.search) + var params = new URLSearchParams(window.location.search); params.delete('finished'); window.location.href = window.location.pathname + '?' + params.toString(); }, @@ -400,7 +400,7 @@ publicWidget.registry.SurveyResultWidget = publicWidget.Widget.extend({ * @param {Event} ev */ _onFilterFinishedClick: function (ev) { - var params = new URLSearchParams(window.location.search) + var params = new URLSearchParams(window.location.search); params.set('finished', true); window.location.href = window.location.pathname + '?' + params.toString(); },