summaryrefslogtreecommitdiff
path: root/addons/survey/controllers/main.py
diff options
context:
space:
mode:
authorDavid Beguin <dbe@odoo.com>2019-04-04 11:56:40 +0000
committerDavid Beguin <dbe@odoo.com>2019-11-19 12:51:55 +0000
commitb1e32976a6297daf2fd5200230c45fe823d26f15 (patch)
tree9cd92191c9ab6741fef1ada5780db0d3c9c51596 /addons/survey/controllers/main.py
parent52a6b00fa704a9c67ff22d28e8b1fbf45fdaa75c (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.py241
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):