diff options
| author | David Beguin <dbe@odoo.com> | 2019-04-04 11:56:40 +0000 |
|---|---|---|
| committer | David Beguin <dbe@odoo.com> | 2019-11-19 12:51:55 +0000 |
| commit | b1e32976a6297daf2fd5200230c45fe823d26f15 (patch) | |
| tree | 9cd92191c9ab6741fef1ada5780db0d3c9c51596 /addons/survey/controllers/main.py | |
| parent | 52a6b00fa704a9c67ff22d28e8b1fbf45fdaa75c (diff) | |
[REF] survey : make survey form a widget
Purpose of this commit is
- to make the survey js controller a widget, to be more 'odoo-standard'.
- to lessen number of RPCs and clean old code for prefilling and validation of
surveys.
Clean Prefill :
Prefill can be done directly in the template as the template has already
all the needed values (in answer object).
Clean Validation :
Validation is done at server side and is independant from prefilling values.
Dates:
Dates are now formatted directly in the template, at rendering, using a
format_date fonction pointer called in the template.
Don't use widget in review mode:
Remove o_survey_form class from review template as only dates were processed in
the widget for review template.
Breadcrumb :
Remove button previous, prev=prev and go_back mechanism :
The previous page is handled by the breadcrump
Remove redirect url mechanism.
Breadcrumb now saves the answers when going to a previous page.
Move o_survey_form class to a higher div to englobe breadcrumb in the widget
and ease his handling.
Remove locale load as already done in session.js#load_modules
This commit modifies the route type of survey submit to work in json.
The js survey form controller calls now manually the route via rpc.
Task ID : 1930132
PR #32419
Diffstat (limited to 'addons/survey/controllers/main.py')
| -rw-r--r-- | addons/survey/controllers/main.py | 241 |
1 files changed, 74 insertions, 167 deletions
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py index 63a463e95a9..3b63cd3dd38 100644 --- a/addons/survey/controllers/main.py +++ b/addons/survey/controllers/main.py @@ -13,7 +13,7 @@ from odoo import fields, http, _ from odoo.addons.base.models.ir_ui_view import keep_query from odoo.exceptions import UserError from odoo.http import request, content_disposition -from odoo.tools import ustr +from odoo.tools import ustr, format_datetime, format_date _logger = logging.getLogger(__name__) @@ -223,36 +223,32 @@ class Survey(http.Controller): else: return request.redirect('/survey/fill/%s/%s' % (survey_sudo.access_token, answer_sudo.token)) - # Survey direct link to a specific page - @http.route('/survey/page/<string:survey_token>/<string:answer_token>/<int:page_id>', - type='http', auth='public', website=True) - def survey_change_page(self, survey_token, answer_token, page_id, **post): - """ Method called when the user switches from one page to another using the breadcrumbs links - in the survey layout. - TODO: Right now, the answers that are not submitted are LOST when changing from one page to another - using this method. - - The survey "submit" mechanism needs to be refactored entirely to make this more user-friendly.""" - # Controls if the survey can be displayed - access_data = self._get_access_data(survey_token, answer_token, ensure_token=False) + @http.route('/survey/fill/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True) + def survey_display_page(self, survey_token, answer_token, **post): + access_data = self._get_access_data(survey_token, answer_token, ensure_token=True) if access_data['validity_code'] is not True: return self._redirect_with_error(access_data, access_data['validity_code']) survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo'] - return request.render('survey.survey', { - 'survey': survey_sudo, - 'page': request.env['survey.question'].sudo().browse(page_id), - 'answer': answer_sudo - }) + data = { + 'format_datetime': lambda dt: format_datetime(request.env, dt, dt_format=False), + 'format_date': lambda date: format_date(request.env, date) + } - @http.route('/survey/fill/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True) - def survey_display_page(self, survey_token, answer_token, prev=None, **post): - access_data = self._get_access_data(survey_token, answer_token, ensure_token=True) - if access_data['validity_code'] is not True: - return self._redirect_with_error(access_data, access_data['validity_code']) + page_or_question_key = 'question' if survey_sudo.questions_layout == 'page_per_question' else 'page' - survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo'] + # Bypass all if page_id is specified (comes from breadcrumb or previous button) + if 'previous_page_id' in post: + previous_page_or_question_id = int(post['previous_page_id']) + new_previous_id = survey_sudo._previous_page_or_question_id(answer_sudo, previous_page_or_question_id) + data.update({ + 'survey': survey_sudo, + page_or_question_key: request.env['survey.question'].sudo().browse(previous_page_or_question_id), + 'answer': answer_sudo, + 'previous_page_id': new_previous_id + }) + return request.render('survey.survey', data) if survey_sudo.is_time_limited and not answer_sudo.start_datetime: # init start date when user starts filling in the survey @@ -260,182 +256,92 @@ class Survey(http.Controller): 'start_datetime': fields.Datetime.now() }) - page_or_question_key = 'question' if survey_sudo.questions_layout == 'page_per_question' else 'page' # Select the right page if answer_sudo.state == 'new': # First page - page_or_question_id, last = survey_sudo.next_page_or_question(answer_sudo, 0, go_back=False) - data = { + page_or_question_id, is_last = survey_sudo.next_page_or_question(answer_sudo, 0) + data.update({ 'survey': survey_sudo, page_or_question_key: page_or_question_id, - 'answer': answer_sudo - } - if last: + 'answer': answer_sudo, + }) + if is_last: data.update({'last': True}) return request.render('survey.survey', data) elif answer_sudo.state == 'done': # Display success message return request.render('survey.sfinished', self._prepare_survey_finished_values(survey_sudo, answer_sudo)) elif answer_sudo.state == 'skip': - flag = (True if prev and prev == 'prev' else False) - page_or_question_id, last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id, go_back=flag) + page_or_question_id, is_last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id) + previous_id = survey_sudo._previous_page_or_question_id(answer_sudo, page_or_question_id.id) - #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_or_question_id: - page_or_question_id, last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id, go_back=True) - - data = { + data.update({ 'survey': survey_sudo, page_or_question_key: page_or_question_id, - 'answer': answer_sudo - } - if last: + 'answer': answer_sudo, + 'previous_page_id': previous_id + }) + if is_last: data.update({'last': True}) return request.render('survey.survey', data) else: return request.render("survey.403", {'survey': survey_sudo}) - @http.route('/survey/prefill/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True) - def survey_get_answers(self, survey_token, answer_token, page_or_question_id=None, **post): - """ TDE NOTE: original comment: # AJAX prefilling of a survey -> AJAX / http ?? """ - access_data = self._get_access_data(survey_token, answer_token, ensure_token=True) - if access_data['validity_code'] is not True and access_data['validity_code'] != 'answer_done': - return {} - - survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo'] - try: - page_or_question_id = int(page_or_question_id) - except: - page_or_question_id = None - - # Fetch previous answers - if survey_sudo.questions_layout == 'one_page' or not page_or_question_id: - previous_answers = answer_sudo.user_input_line_ids - elif survey_sudo.questions_layout == 'page_per_section': - previous_answers = answer_sudo.user_input_line_ids.filtered(lambda line: line.page_id.id == page_or_question_id) - else: - previous_answers = answer_sudo.user_input_line_ids.filtered(lambda line: line.question_id.id == page_or_question_id) - - # Return non empty answers in a JSON compatible format - ret = {} - for answer in previous_answers: - if not answer.skipped: - answer_tag = '%s_%s' % (answer.survey_id.id, answer.question_id.id) - answer_value = None - if answer.answer_type == 'free_text': - answer_value = answer.value_free_text - elif answer.answer_type == 'text' and answer.question_id.question_type == 'textbox': - answer_value = answer.value_text - elif answer.answer_type == 'text' and answer.question_id.question_type != 'textbox': - # here come comment answers for matrices, simple choice and multiple choice - answer_tag = "%s_%s" % (answer_tag, 'comment') - answer_value = answer.value_text - elif answer.answer_type == 'number': - answer_value = str(answer.value_number) - elif answer.answer_type == 'date': - answer_value = fields.Datetime.to_string(answer.value_date) - elif answer.answer_type == 'datetime': - answer_value = fields.Datetime.to_string(answer.value_datetime) - elif answer.answer_type == 'suggestion' and not answer.value_suggested_row: - answer_value = answer.value_suggested.id - elif answer.answer_type == 'suggestion' and answer.value_suggested_row: - answer_tag = "%s_%s" % (answer_tag, answer.value_suggested_row.id) - answer_value = answer.value_suggested.id - if answer_value: - ret.setdefault(answer_tag, []).append(answer_value) - else: - _logger.warning("[survey] No answer has been found for question %s marked as non skipped" % answer_tag) - return json.dumps(ret, default=str) - - @http.route('/survey/scores/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True) - def survey_get_scores(self, survey_id, answer_token, page_id=None, **post): - """ TDE NOTE: original comment: # AJAX scores loading for quiz correction mode -> AJAX / http ?? """ - access_data = self._get_access_data(survey_id, answer_token, ensure_token=True) - if access_data['validity_code'] is not True: - return {} - - survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo'] - - # Compute score for each question - ret = {} - for answer in answer_sudo.user_input_line_ids: - tmp_score = ret.get(answer.question_id.id, 0.0) - ret.update({answer.question_id.id: tmp_score + answer.answer_score}) - return json.dumps(ret) - - @http.route('/survey/submit/<string:survey_token>/<string:answer_token>', type='http', methods=['POST'], auth='public', website=True) + @http.route('/survey/submit/<string:survey_token>/<string:answer_token>', type='json', auth='public', website=True) def survey_submit(self, survey_token, answer_token, **post): """ Submit a page from the survey. 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 wil be ignored and - survey state will be forced to 'done' - - TDE NOTE: original comment: # AJAX submission of a page -> AJAX / http ?? """ + If the time limit is reached, errors will be skipped, answers will be ignored and + survey state will be forced to 'done'""" + # Validation access_data = self._get_access_data(survey_token, answer_token, ensure_token=True) if access_data['validity_code'] is not True: - return {} - + return { + 'error': access_data['validity_code'], + } survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo'] + + questions, page_or_question_id = survey_sudo._get_survey_questions(answer=answer_sudo, + 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 {} - if survey_sudo.questions_layout == 'page_per_section': - page_id = int(post['page_id']) - questions = request.env['survey.question'].sudo().search([('survey_id', '=', survey_sudo.id), ('page_id', '=', page_id)]) - # we need the intersection of the questions of this page AND the questions prepared for that user_input - # (because randomized surveys do not use all the questions of every page) - questions = questions & answer_sudo.question_ids - page_or_question_id = page_id - elif survey_sudo.questions_layout == 'page_per_question': - question_id = int(post['question_id']) - questions = request.env['survey.question'].sudo().browse(question_id) - page_or_question_id = question_id - else: - questions = survey_sudo.question_ids - questions = questions & answer_sudo.question_ids - - errors = {} - # Answer validation + # Submitting survey if not answer_sudo.is_time_limit_reached: for question in questions: answer_tag = "%s_%s" % (survey_sudo.id, question.id) - errors.update(question.validate_question(post, answer_tag)) - - ret = {} - if len(errors): - # Return errors messages to webpage - ret['errors'] = errors - else: - if not answer_sudo.is_time_limit_reached: - 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) - - vals = {} - if answer_sudo.is_time_limit_reached or survey_sudo.questions_layout == 'one_page': - go_back = False + request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, post, answer_tag) + + 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: + 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} + + if next_page is None: answer_sudo._mark_done() - elif 'button_submit' in post: - go_back = post['button_submit'] == 'previous' - next_page, last = request.env['survey.survey'].next_page_or_question(answer_sudo, page_or_question_id, go_back=go_back) - vals = {'last_displayed_page_id': page_or_question_id} - - if next_page is None and not go_back: - answer_sudo._mark_done() - else: - vals.update({'state': 'skip'}) - - if 'breadcrumb_redirect' in post: - ret['redirect'] = post['breadcrumb_redirect'] else: - if vals: - answer_sudo.write(vals) + vals.update({'state': 'skip'}) - ret['redirect'] = '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token) - if go_back: - ret['redirect'] += '?prev=prev' + answer_sudo.write(vals) - return json.dumps(ret) + return '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token) # ------------------------------------------------------------ # COMPLETED SURVEY ROUTES @@ -460,8 +366,9 @@ class Survey(http.Controller): 'review': review, 'survey': survey_sudo, 'answer': answer_sudo, - 'page_nr': 0, - 'quizz_correction': survey_sudo.scoring_type != 'scoring_without_answers' and answer_sudo}) + 'format_datetime': lambda dt: format_datetime(request.env, dt, dt_format=False), + 'format_date': lambda date: format_date(request.env, date) + }) @http.route('/survey/results/<model("survey.survey"):survey>', type='http', auth='user', website=True) def survey_report(self, survey, answer_token=None, **post): |
