[FIX] website_event: display unconfirmed events too
[odoo/odoo.git] / addons / survey / controllers / main.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import json
23 import logging
24 import werkzeug
25 from datetime import datetime
26 from math import ceil
27
28 from openerp import SUPERUSER_ID
29 from openerp.addons.web import http
30 from openerp.addons.web.http import request
31 from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DTF
32 from openerp.tools.safe_eval import safe_eval
33
34
35 _logger = logging.getLogger(__name__)
36
37
38 class WebsiteSurvey(http.Controller):
39
40     ## HELPER METHODS ##
41
42     def _check_bad_cases(self, cr, uid, request, survey_obj, survey, user_input_obj, context=None):
43         # In case of bad survey, redirect to surveys list
44         if survey_obj.exists(cr, SUPERUSER_ID, survey.id, context=context) == []:
45             return werkzeug.utils.redirect("/survey/")
46
47         # In case of auth required, block public user
48         if survey.auth_required and uid == request.website.user_id.id:
49             return request.website.render("survey.auth_required", {'survey': survey})
50
51         # In case of non open surveys
52         if survey.stage_id.closed:
53             return request.website.render("survey.notopen")
54
55         # If there is no pages
56         if not survey.page_ids:
57             return request.website.render("survey.nopages")
58
59         # Everything seems to be ok
60         return None
61
62     def _check_deadline(self, cr, uid, user_input, context=None):
63         '''Prevent opening of the survey if the deadline has turned out
64
65         ! This will NOT disallow access to users who have already partially filled the survey !'''
66         if user_input.deadline:
67             dt_deadline = datetime.strptime(user_input.deadline, DTF)
68             dt_now = datetime.now()
69             if dt_now > dt_deadline:  # survey is not open anymore
70                 return request.website.render("survey.notopen")
71
72         return None
73
74     ## ROUTES HANDLERS ##
75
76     # Survey start
77     @http.route(['/survey/start/<model("survey.survey"):survey>',
78                  '/survey/start/<model("survey.survey"):survey>/<string:token>'],
79                 type='http', auth='public', website=True)
80     def start_survey(self, survey, token=None, **post):
81         cr, uid, context = request.cr, request.uid, request.context
82         survey_obj = request.registry['survey.survey']
83         user_input_obj = request.registry['survey.user_input']
84
85         # Test mode
86         if token and token == "phantom":
87             _logger.info("[survey] Phantom mode")
88             user_input_id = user_input_obj.create(cr, uid, {'survey_id': survey.id, 'test_entry': True}, context=context)
89             user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
90             data = {'survey': survey, 'page': None, 'token': user_input.token}
91             return request.website.render('survey.survey_init', data)
92         # END Test mode
93
94         # Controls if the survey can be displayed
95         errpage = self._check_bad_cases(cr, uid, request, survey_obj, survey, user_input_obj, context=context)
96         if errpage:
97             return errpage
98
99         # Manual surveying
100         if not token:
101             user_input_id = user_input_obj.create(cr, uid, {'survey_id': survey.id}, context=context)
102             user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
103         else:
104             try:
105                 user_input_id = user_input_obj.search(cr, uid, [('token', '=', token)], context=context)[0]
106             except IndexError:  # Invalid token
107                 return request.website.render("website.403")
108             else:
109                 user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
110
111         # Do not open expired survey
112         errpage = self._check_deadline(cr, uid, user_input, context=context)
113         if errpage:
114             return errpage
115
116         # Select the right page
117         if user_input.state == 'new':  # Intro page
118             data = {'survey': survey, 'page': None, 'token': user_input.token}
119             return request.website.render('survey.survey_init', data)
120         else:
121             return request.redirect('/survey/fill/%s/%s' % (survey.id, user_input.token))
122
123     # Survey displaying
124     @http.route(['/survey/fill/<model("survey.survey"):survey>/<string:token>',
125                  '/survey/fill/<model("survey.survey"):survey>/<string:token>/<string:prev>'],
126                 type='http', auth='public', website=True)
127     def fill_survey(self, survey, token, prev=None, **post):
128         '''Display and validates a survey'''
129         cr, uid, context = request.cr, request.uid, request.context
130         survey_obj = request.registry['survey.survey']
131         user_input_obj = request.registry['survey.user_input']
132
133         # Controls if the survey can be displayed
134         errpage = self._check_bad_cases(cr, uid, request, survey_obj, survey, user_input_obj, context=context)
135         if errpage:
136             return errpage
137
138         # Load the user_input
139         try:
140             user_input_id = user_input_obj.search(cr, uid, [('token', '=', token)])[0]
141         except IndexError:  # Invalid token
142             return request.website.render("website.403")
143         else:
144             user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
145
146         # Do not display expired survey (even if some pages have already been
147         # displayed -- There's a time for everything!)
148         errpage = self._check_deadline(cr, uid, user_input, context=context)
149         if errpage:
150             return errpage
151
152         # Select the right page
153         if user_input.state == 'new':  # First page
154             page, page_nr, last = survey_obj.next_page(cr, uid, user_input, 0, go_back=False, context=context)
155             data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
156             if last:
157                 data.update({'last': True})
158             return request.website.render('survey.survey', data)
159         elif user_input.state == 'done':  # Display success message
160             return request.website.render('survey.sfinished', {'survey': survey,
161                                                                'token': token,
162                                                                'user_input': user_input})
163         elif user_input.state == 'skip':
164             flag = (True if prev and prev == 'prev' else False)
165             page, page_nr, last = survey_obj.next_page(cr, uid, user_input, user_input.last_displayed_page_id.id, go_back=flag, context=context)
166             data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
167             if last:
168                 data.update({'last': True})
169             return request.website.render('survey.survey', data)
170         else:
171             return request.website.render("website.403")
172
173     # AJAX prefilling of a survey
174     @http.route(['/survey/prefill/<model("survey.survey"):survey>/<string:token>',
175                  '/survey/prefill/<model("survey.survey"):survey>/<string:token>/<model("survey.page"):page>'],
176                 type='http', auth='public', website=True)
177     def prefill(self, survey, token, page=None, **post):
178         cr, uid, context = request.cr, request.uid, request.context
179         user_input_line_obj = request.registry['survey.user_input_line']
180         ret = {}
181
182         # Fetch previous answers
183         if page:
184             ids = user_input_line_obj.search(cr, uid, [('user_input_id.token', '=', token), ('page_id', '=', page.id)], context=context)
185         else:
186             ids = user_input_line_obj.search(cr, uid, [('user_input_id.token', '=', token)], context=context)
187         previous_answers = user_input_line_obj.browse(cr, uid, ids, context=context)
188
189         # Return non empty answers in a JSON compatible format
190         for answer in previous_answers:
191             if not answer.skipped:
192                 answer_tag = '%s_%s_%s' % (answer.survey_id.id, answer.page_id.id, answer.question_id.id)
193                 answer_value = None
194                 if answer.answer_type == 'free_text':
195                     answer_value = answer.value_free_text
196                 elif answer.answer_type == 'text' and answer.question_id.type == 'textbox':
197                     answer_value = answer.value_text
198                 elif answer.answer_type == 'text' and answer.question_id.type != 'textbox':
199                     # here come comment answers for matrices, simple choice and multiple choice
200                     answer_tag = "%s_%s" % (answer_tag, 'comment')
201                     answer_value = answer.value_text
202                 elif answer.answer_type == 'number':
203                     answer_value = answer.value_number.__str__()
204                 elif answer.answer_type == 'date':
205                     answer_value = answer.value_date
206                 elif answer.answer_type == 'suggestion' and not answer.value_suggested_row:
207                     answer_value = answer.value_suggested.id
208                 elif answer.answer_type == 'suggestion' and answer.value_suggested_row:
209                     answer_tag = "%s_%s" % (answer_tag, answer.value_suggested_row.id)
210                     answer_value = answer.value_suggested.id
211                 if answer_value:
212                     dict_soft_update(ret, answer_tag, answer_value)
213                 else:
214                     _logger.warning("[survey] No answer has been found for question %s marked as non skipped" % answer_tag)
215         return json.dumps(ret)
216
217     # AJAX scores loading for quiz correction mode
218     @http.route(['/survey/scores/<model("survey.survey"):survey>/<string:token>'],
219                 type='http', auth='public', website=True)
220     def get_scores(self, survey, token, page=None, **post):
221         cr, uid, context = request.cr, request.uid, request.context
222         user_input_line_obj = request.registry['survey.user_input_line']
223         ret = {}
224
225         # Fetch answers
226         ids = user_input_line_obj.search(cr, uid, [('user_input_id.token', '=', token)], context=context)
227         previous_answers = user_input_line_obj.browse(cr, uid, ids, context=context)
228
229         # Compute score for each question
230         for answer in previous_answers:
231             tmp_score = ret.get(answer.question_id.id, 0.0)
232             ret.update({answer.question_id.id: tmp_score + answer.quizz_mark})
233         return json.dumps(ret)
234
235     # AJAX submission of a page
236     @http.route(['/survey/submit/<model("survey.survey"):survey>'],
237                 type='http', methods=['POST'], auth='public', website=True)
238     def submit(self, survey, **post):
239         _logger.debug('Incoming data: %s', post)
240         page_id = int(post['page_id'])
241         cr, uid, context = request.cr, request.uid, request.context
242         survey_obj = request.registry['survey.survey']
243         questions_obj = request.registry['survey.question']
244         questions_ids = questions_obj.search(cr, uid, [('page_id', '=', page_id)], context=context)
245         questions = questions_obj.browse(cr, uid, questions_ids, context=context)
246
247         # Answer validation
248         errors = {}
249         for question in questions:
250             answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
251             errors.update(questions_obj.validate_question(cr, uid, question, post, answer_tag, context=context))
252
253         ret = {}
254         if (len(errors) != 0):
255             # Return errors messages to webpage
256             ret['errors'] = errors
257         else:
258             # Store answers into database
259             user_input_obj = request.registry['survey.user_input']
260
261             user_input_line_obj = request.registry['survey.user_input_line']
262             try:
263                 user_input_id = user_input_obj.search(cr, uid, [('token', '=', post['token'])], context=context)[0]
264             except KeyError:  # Invalid token
265                 return request.website.render("website.403")
266             for question in questions:
267                 answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
268                 user_input_line_obj.save_lines(cr, uid, user_input_id, question, post, answer_tag, context=context)
269
270             user_input = user_input_obj.browse(cr, uid, user_input_id, context=context)
271             go_back = post['button_submit'] == 'previous'
272             next_page, _, last = survey_obj.next_page(cr, uid, user_input, page_id, go_back=go_back, context=context)
273             vals = {'last_displayed_page_id': page_id}
274             if next_page is None and not go_back:
275                 vals.update({'state': 'done'})
276             else:
277                 vals.update({'state': 'skip'})
278             user_input_obj.write(cr, uid, user_input_id, vals, context=context)
279             ret['redirect'] = '/survey/fill/%s/%s' % (survey.id, post['token'])
280             if go_back:
281                 ret['redirect'] += '/prev'
282         return json.dumps(ret)
283
284     # Printing routes
285     @http.route(['/survey/print/<model("survey.survey"):survey>',
286                  '/survey/print/<model("survey.survey"):survey>/<string:token>'],
287                 type='http', auth='public', website=True)
288     def print_survey(self, survey, token=None, **post):
289         '''Display an survey in printable view; if <token> is set, it will
290         grab the answers of the user_input_id that has <token>.'''
291         return request.website.render('survey.survey_print',
292                                       {'survey': survey,
293                                        'token': token,
294                                        'page_nr': 0,
295                                        'quizz_correction': True if survey.quizz_mode and token else False})
296
297     @http.route(['/survey/results/<model("survey.survey"):survey>'],
298                 type='http', auth='user', website=True)
299     def survey_reporting(self, survey, token=None, **post):
300         '''Display survey Results & Statistics for given survey.'''
301         result_template, current_filters, filter_display_data, filter_finish = 'survey.result', [], [], False
302         survey_obj = request.registry['survey.survey']
303         if not survey.user_input_ids or not [input_id.id for input_id in survey.user_input_ids if input_id.state != 'new']:
304             result_template = 'survey.no_result'
305         if 'finished' in post:
306             post.pop('finished')
307             filter_finish = True
308         if post or filter_finish:
309             filter_data = self.get_filter_data(post)
310             current_filters = survey_obj.filter_input_ids(request.cr, request.uid, filter_data, filter_finish, context=request.context)
311             filter_display_data = survey_obj.get_filter_display_data(request.cr, request.uid, filter_data, context=request.context)
312         return request.website.render(result_template,
313                                       {'survey': survey,
314                                        'survey_dict': self.prepare_result_dict(survey, current_filters),
315                                        'page_range': self.page_range,
316                                        'current_filters': current_filters,
317                                        'filter_display_data': filter_display_data,
318                                        'filter_finish': filter_finish
319                                        })
320
321     def prepare_result_dict(self,survey, current_filters=[]):
322         """Returns dictionary having values for rendering template"""
323         survey_obj = request.registry['survey.survey']
324         result = {'survey':survey, 'page_ids': []}
325         for page in survey.page_ids:
326             page_dict = {'page': page, 'question_ids': []}
327             for question in page.question_ids:
328                 question_dict = {'question':question, 'input_summary':survey_obj.get_input_summary(request.cr, request.uid, question, current_filters, context=request.context), 'prepare_result':survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context), 'graph_data': self.get_graph_data(question, current_filters)}
329                 page_dict['question_ids'].append(question_dict)
330             result['page_ids'].append(page_dict)
331         return result
332
333     def get_filter_data(self, post):
334         """Returns data used for filtering the result"""
335         filters = []
336         for ids in post:
337             #if user add some random data in query URI, ignore it
338             try:
339                 row_id, answer_id = ids.split(',')
340                 filters.append({'row_id': int(row_id), 'answer_id': int(answer_id)})
341             except:
342                 return filters
343         return filters
344
345     def page_range(self, total_record, limit):
346         '''Returns number of pages required for pagination'''
347         total = ceil(total_record / float(limit))
348         return range(1, int(total + 1))
349
350     def get_graph_data(self, question, current_filters=[]):
351         '''Returns formatted data required by graph library on basis of filter'''
352         survey_obj = request.registry['survey.survey']
353         result = []
354         if question.type == 'multiple_choice':
355             result.append({'key': str(question.question),
356                            'values': survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)})
357         if question.type == 'simple_choice':
358             result = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)
359         if question.type == 'matrix':
360             data = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)
361             for answer in data['answers']:
362                 values = []
363                 for res in data['result']:
364                     if res[1] == answer:
365                         values.append({'text': data['rows'][res[0]], 'count': data['result'][res]})
366                 result.append({'key': data['answers'].get(answer), 'values': values})
367         return json.dumps(result)
368
369 def dict_soft_update(dictionary, key, value):
370     ''' Insert the pair <key>: <value> into the <dictionary>. If <key> is
371     already present, this function will append <value> to the list of
372     existing data (instead of erasing it) '''
373     if key in dictionary:
374         dictionary[key].append(value)
375     else:
376         dictionary.update({key: [value]})