summaryrefslogtreecommitdiff
path: root/addons/survey/controllers/survey_session_manage.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/survey/controllers/survey_session_manage.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/survey/controllers/survey_session_manage.py')
-rw-r--r--addons/survey/controllers/survey_session_manage.py241
1 files changed, 241 insertions, 0 deletions
diff --git a/addons/survey/controllers/survey_session_manage.py b/addons/survey/controllers/survey_session_manage.py
new file mode 100644
index 00000000..04923c99
--- /dev/null
+++ b/addons/survey/controllers/survey_session_manage.py
@@ -0,0 +1,241 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import datetime
+import json
+import werkzeug
+
+from dateutil.relativedelta import relativedelta
+from werkzeug.exceptions import NotFound
+
+from odoo import fields, http
+from odoo.http import request
+from odoo.tools import is_html_empty
+
+
+class UserInputSession(http.Controller):
+ def _fetch_from_token(self, survey_token):
+ """ Check that given survey_token matches a survey 'access_token'.
+ Unlike the regular survey controller, user trying to access the survey must have full access rights! """
+ return request.env['survey.survey'].search([('access_token', '=', survey_token)])
+
+ def _fetch_from_session_code(self, session_code):
+ """ Matches a survey against a passed session_code.
+ We force the session_state to be reachable (ready / in_progress) to avoid people
+ using this route to access other (private) surveys.
+ We limit to sessions opened within the last 7 days to avoid potential abuses. """
+ if session_code:
+ matching_survey = request.env['survey.survey'].sudo().search([
+ ('state', '=', 'open'),
+ ('session_state', 'in', ['ready', 'in_progress']),
+ ('session_start_time', '>', fields.Datetime.now() - relativedelta(days=7)),
+ ('session_code', '=', session_code),
+ ], limit=1)
+ if matching_survey:
+ return matching_survey
+
+ return False
+
+ # ------------------------------------------------------------
+ # SURVEY SESSION MANAGEMENT
+ # ------------------------------------------------------------
+
+ @http.route('/survey/session/manage/<string:survey_token>', type='http', auth='user', website=True)
+ def survey_session_manage(self, survey_token, **kwargs):
+ """ Main route used by the host to 'manager' the session.
+ - If the state of the session is 'ready'
+ We render a template allowing the host to showcase the different options of the session
+ and to actually start the session.
+ - If the state of the session is 'in_progress'
+ We render a template allowing the host to show the question results, display the attendees
+ leaderboard or go to the next question of the session. """
+
+ survey = self._fetch_from_token(survey_token)
+
+ if not survey or not survey.session_state:
+ # no open session
+ return NotFound()
+
+ if survey.session_state == 'ready':
+ return request.render('survey.user_input_session_open', {
+ 'survey': survey
+ })
+ else:
+ template_values = self._prepare_manage_session_values(survey)
+ return request.render('survey.user_input_session_manage', template_values)
+
+ @http.route('/survey/session/next_question/<string:survey_token>', type='json', auth='user', website=True)
+ def survey_session_next_question(self, survey_token, **kwargs):
+ """ This route is called when the host goes to the next question of the session.
+
+ It's not a regular 'request.render' route because we handle the transition between
+ questions using a AJAX call to be able to display a bioutiful fade in/out effect.
+
+ It triggers the next question of the session.
+
+ We artificially add 1 second to the 'current_question_start_time' to account for server delay.
+ As the timing can influence the attendees score, we try to be fair with everyone by giving them
+ an extra second before we start counting down.
+
+ Frontend should take the delay into account by displaying the appropriate animations.
+
+ Writing the next question on the survey is sudo'ed to avoid potential access right issues.
+ e.g: a survey user can create a live session from any survey but he can only write
+ on its own survey. """
+
+ survey = self._fetch_from_token(survey_token)
+
+ if not survey or not survey.session_state:
+ # no open session
+ return ''
+
+ if survey.session_state == 'ready':
+ survey._session_open()
+
+ next_question = survey._get_session_next_question()
+
+ # using datetime.datetime because we want the millis portion
+ if next_question:
+ now = datetime.datetime.now()
+ survey.sudo().write({
+ 'session_question_id': next_question.id,
+ 'session_question_start_time': fields.Datetime.now() + relativedelta(seconds=1)
+ })
+ request.env['bus.bus'].sendone(survey.access_token, {
+ 'question_start': now.timestamp(),
+ 'type': 'next_question'
+ })
+
+ template_values = self._prepare_manage_session_values(survey)
+ template_values['is_rpc_call'] = True
+ return request.env.ref('survey.user_input_session_manage_content')._render(template_values)
+ else:
+ return False
+
+ @http.route('/survey/session/results/<string:survey_token>', type='json', auth='user', website=True)
+ def survey_session_results(self, survey_token, **kwargs):
+ """ This route is called when the host shows the current question's results.
+
+ It's not a regular 'request.render' route because we handle the display of results using
+ an AJAX request to be able to include the results in the currently displayed page. """
+
+ survey = self._fetch_from_token(survey_token)
+
+ if not survey or survey.session_state != 'in_progress':
+ # no open session
+ return False
+
+ user_input_lines = request.env['survey.user_input.line'].search([
+ ('survey_id', '=', survey.id),
+ ('question_id', '=', survey.session_question_id.id),
+ ('create_date', '>=', survey.session_start_time)
+ ])
+
+ return self._prepare_question_results_values(survey, user_input_lines)
+
+ @http.route('/survey/session/leaderboard/<string:survey_token>', type='json', auth='user', website=True)
+ def survey_session_leaderboard(self, survey_token, **kwargs):
+ """ This route is called when the host shows the current question's attendees leaderboard.
+
+ It's not a regular 'request.render' route because we handle the display of the leaderboard
+ using an AJAX request to be able to include the results in the currently displayed page. """
+
+ survey = self._fetch_from_token(survey_token)
+
+ if not survey or survey.session_state != 'in_progress':
+ # no open session
+ return ''
+
+ return request.env.ref('survey.user_input_session_leaderboard')._render({
+ 'animate': True,
+ 'leaderboard': survey._prepare_leaderboard_values()
+ })
+
+ # ------------------------------------------------------------
+ # QUICK ACCESS SURVEY ROUTES
+ # ------------------------------------------------------------
+
+ @http.route('/s', type='http', auth='public', website=True, sitemap=False)
+ def survey_session_code(self, **post):
+ """ Renders the survey session code page route.
+ This page allows the user to enter the session code of the survey.
+ It is mainly used to ease survey access for attendees in session mode. """
+ return request.render("survey.survey_session_code")
+
+ @http.route('/s/<string:session_code>', type='http', auth='public', website=True)
+ def survey_start_short(self, session_code):
+ """" Redirects to 'survey_start' route using a shortened link & token.
+ We match the session_code for open surveys.
+ This route is used in survey sessions where we need short links for people to type. """
+
+ survey = self._fetch_from_session_code(session_code)
+ if survey:
+ return werkzeug.utils.redirect("/survey/start/%s" % survey.access_token)
+
+ return werkzeug.utils.redirect("/s")
+
+ @http.route('/survey/check_session_code/<string:session_code>', type='json', auth='public', website=True)
+ def survey_check_session_code(self, session_code):
+ """ Checks if the given code is matching a survey session_code.
+ If yes, redirect to /s/code route.
+ If not, return error. The user is invited to type again the code. """
+ survey = self._fetch_from_session_code(session_code)
+ if survey:
+ return {"survey_url": "/survey/start/%s" % survey.access_token}
+
+ return {"error": "survey_wrong"}
+
+ def _prepare_manage_session_values(self, survey):
+ is_last_question = False
+ if survey.question_ids:
+ most_voted_answers = survey._get_session_most_voted_answers()
+ is_last_question = survey._is_last_page_or_question(most_voted_answers, survey.session_question_id)
+
+ values = {
+ 'survey': survey,
+ 'is_last_question': is_last_question,
+ }
+
+ values.update(self._prepare_question_results_values(survey, request.env['survey.user_input.line']))
+
+ return values
+
+ def _prepare_question_results_values(self, survey, user_input_lines):
+ """ Prepares usefull values to display during the host session:
+
+ - question_statistics_graph
+ The graph data to display the bar chart for questions of type 'choice'
+ - input_lines_values
+ The answer values to text/date/datetime questions
+ - answers_validity
+ An array containing the is_correct value for all question answers.
+ We need this special variable because of Chartjs data structure.
+ The library determines the parameters (color/label/...) by only passing the answer 'index'
+ (and not the id or anything else we can identify).
+ In other words, we need to know if the answer at index 2 is correct or not.
+ - answer_count
+ The number of answers to the current question. """
+
+ question = survey.session_question_id
+ answers_validity = []
+ if (any(answer.is_correct for answer in question.suggested_answer_ids)):
+ answers_validity = [answer.is_correct for answer in question.suggested_answer_ids]
+ if question.comment_count_as_answer:
+ answers_validity.append(False)
+
+ full_statistics = question._prepare_statistics(user_input_lines)[0]
+ input_line_values = []
+ if question.question_type in ['char_box', 'date', 'datetime']:
+ input_line_values = [{
+ 'id': line.id,
+ 'value': line['value_%s' % question.question_type]
+ } for line in full_statistics.get('table_data', request.env['survey.user_input.line'])[:100]]
+
+ return {
+ 'is_html_empty': is_html_empty,
+ 'question_statistics_graph': full_statistics.get('graph_data'),
+ 'input_line_values': input_line_values,
+ 'answers_validity': json.dumps(answers_validity),
+ 'answer_count': survey.session_question_answer_count,
+ 'attendees_count': survey.session_answer_count,
+ }