diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py index 15a4003c1ac..06659b6df9e 100644 --- a/addons/survey/controllers/main.py +++ b/addons/survey/controllers/main.py @@ -22,9 +22,7 @@ import json import logging import werkzeug -from collections import Counter from datetime import datetime -from itertools import product from math import ceil from openerp import SUPERUSER_ID @@ -287,118 +285,65 @@ class WebsiteSurvey(http.Controller): type='http', auth='user', multilang=True, website=True) def survey_reporting(self, survey, token=None, **post): '''Display survey Results & Statistics for given survey.''' - result_template, current_filters, filter_display_data = 'survey.result', [], [] + result_template, current_filters, filter_display_data, filter_finish = 'survey.result', [], [], False + survey_obj = request.registry['survey.survey'] if not survey.user_input_ids or not [input_id.id for input_id in survey.user_input_ids if input_id.state != 'new']: result_template = 'survey.no_result' - if post: - current_filters, filter_display_data = self.filter_input_ids(post) + if 'finished' in post: + post.pop('finished') + filter_finish = True + if post or filter_finish: + filter_data = self.get_filter_data(post) + current_filters = survey_obj.filter_input_ids(request.cr, request.uid, filter_data, filter_finish, context=request.context) + filter_display_data = survey_obj.get_filter_display_data(request.cr, request.uid, filter_data, context=request.context) return request.website.render(result_template, - {'survey': survey, - 'prepare_result': self.prepare_result, - 'get_input_summary': self.get_input_summary, - 'get_graph_data': self.get_graph_data, + {'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_display_data': filter_display_data, + 'filter_finish': filter_finish }) - def filter_input_ids(self, filters): - '''If user applies any filters, then this function returns list of - filtered user_input_id and label's strings for display data in web''' - cr, uid, context = request.cr, request.uid, request.context - question_obj = request.registry['survey.question'] - input_obj = request.registry['survey.user_input_line'] - input_line_obj = request.registry['survey.user_input_line'] - label_obj = request.registry['survey.label'] - domain_filter, choice, filter_display_data = [], [], [] + def prepare_result_dict(self,survey, current_filters=[]): + """Returns dictionary having values for rendering template""" + survey_obj = request.registry['survey.survey'] + result = {'survey':survey, 'page_ids': []} + for page in survey.page_ids: + page_dict = {'page': page, 'question_ids': []} + for question in page.question_ids: + 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)} + page_dict['question_ids'].append(question_dict) + result['page_ids'].append(page_dict) + return result - #if user add some random data in query URI - try: - for ids in filters: + def get_filter_data(self, post): + """Returns data used for filtering the result""" + filters = [] + for ids in post: + #if user add some random data in query URI, ignore it + try: row_id, answer_id = ids.split(',') - question_id = filters[ids] - question = question_obj.browse(cr, uid, int(question_id), context=context) - if row_id == '0': - choice.append(int(answer_id)) - labels = label_obj.browse(cr, uid, [int(answer_id)], context=context) - else: - domain_filter.extend(['|', ('value_suggested_row.id', '=', int(row_id)), ('value_suggested.id', '=', int(answer_id))]) - labels = label_obj.browse(cr, uid, [int(row_id), int(answer_id)], context=context) - filter_display_data.append({'question_text': question.question, 'labels': [label.value for label in labels]}) - if choice: - domain_filter.insert(0, ('value_suggested.id', 'in', choice)) - else: - domain_filter = domain_filter[1:] - except: - #if user add some random data in query URI - return([], []) - - line_ids = input_line_obj.search(cr, uid, domain_filter, context=context) - filtered_input_ids = [input.user_input_id.id for input in input_obj.browse(cr, uid, line_ids, context=context)] - return (filtered_input_ids, filter_display_data) + filters.append({'row_id': int(row_id), 'answer_id': int(answer_id)}) + except: + return filters + return filters def page_range(self, total_record, limit): '''Returns number of pages required for pagination''' total = ceil(total_record / float(limit)) return range(1, int(total + 1)) - def prepare_result(self, question, current_filters=[]): - '''Prepare statistical data for questions by counting number of vote per choice on basis of filter''' - - #Calculate and return statistics for choice - if question.type in ['simple_choice', 'multiple_choice']: - result_summary = {} - [result_summary.update({label.id: {'text': label.value, 'count': 0, 'answer_id': label.id}}) for label in question.labels_ids] - for input_line in question.user_input_line_ids: - if result_summary.get(input_line.value_suggested.id) and (not(current_filters) or input_line.user_input_id.id in current_filters): - result_summary[input_line.value_suggested.id]['count'] += 1 - result_summary = result_summary.values() - - #Calculate and return statistics for matrix - if question.type == 'matrix': - rows, answers, res = {}, {}, {} - [rows.update({label.id: label.value}) for label in question.labels_ids_2] - [answers.update({label.id: label.value}) for label in question.labels_ids] - for cell in product(rows.keys(), answers.keys()): - res[cell] = 0 - for input_line in question.user_input_line_ids: - if not(current_filters) or input_line.user_input_id.id in current_filters: - res[(input_line.value_suggested_row.id, input_line.value_suggested.id)] += 1 - result_summary = {'answers': answers, 'rows': rows, 'result': res} - - #Calculate and return statistics for free_text, textbox, datetime - if question.type in ['free_text', 'textbox', 'datetime']: - result_summary = [] - for input_line in question.user_input_line_ids: - if not(current_filters) or input_line.user_input_id.id in current_filters: - result_summary.append(input_line) - - #Calculate and return statistics for numerical_box - if question.type == 'numerical_box': - result_summary = {'input_lines': []} - all_inputs = [] - for input_line in question.user_input_line_ids: - if not(current_filters) or input_line.user_input_id.id in current_filters: - all_inputs.append(input_line.value_number) - result_summary['input_lines'].append(input_line) - result_summary.update({'average': round(sum(all_inputs) / len(all_inputs), 2), - 'max': round(max(all_inputs), 2), - 'min': round(min(all_inputs), 2), - 'most_comman': Counter(all_inputs).most_common(5)}) - - return result_summary - - @http.route(['/survey/results/graph/'], - type='http', auth='user', multilang=True, website=True) - def get_graph_data(self, question, **post): - '''Returns appropriate formated data required by graph library on basis of filter''' - current_filters = safe_eval(post.get('current_filters', '[]')) + def get_graph_data(self, question, current_filters=[]): + '''Returns formatted data required by graph library on basis of filter''' + survey_obj = request.registry['survey.survey'] result = [] - if question.type in ['simple_choice', 'multiple_choice']: + if question.type == 'multiple_choice': result.append({'key': str(question.question), - 'values': self.prepare_result(question, current_filters)}) + 'values': survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)}) + if question.type == 'simple_choice': + result = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context) if question.type == 'matrix': - data = self.prepare_result(question, current_filters) + data = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context) for answer in data['answers']: values = [] for res in data['result']: @@ -407,21 +352,6 @@ class WebsiteSurvey(http.Controller): result.append({'key': data['answers'].get(answer), 'values': values}) return json.dumps(result) - def get_input_summary(self, question, current_filters=[]): - '''Returns overall summary of question e.g. answered, skipped, total_inputs on basis of filter''' - result = {} - if question.survey_id.user_input_ids: - total_input_ids = current_filters or [input_id.id for input_id in question.survey_id.user_input_ids if input_id.state != 'new'] - result['total_inputs'] = len(total_input_ids) - question_input_ids = [] - for user_input in question.user_input_line_ids: - if not user_input.skipped: - question_input_ids.append(user_input.user_input_id.id) - result['answered'] = len(set(question_input_ids) & set(total_input_ids)) - result['skipped'] = result['total_inputs'] - result['answered'] - return result - - def dict_soft_update(dictionary, key, value): ''' Insert the pair : into the . If is already present, this function will append to the list of diff --git a/addons/survey/static/src/css/survey_result.css b/addons/survey/static/src/css/survey_result.css index 2033d9275fd..9762bc3866f 100644 --- a/addons/survey/static/src/css/survey_result.css +++ b/addons/survey/static/src/css/survey_result.css @@ -1,42 +1,53 @@ -.only_right_radius{ - border-top-right-radius:2em; - border-bottom-right-radius:2em; - border-top-left-radius:0; - border-bottom-left-radius:0; +.only_right_radius { + border-top-right-radius: 2em; + border-bottom-right-radius: 2em; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } -.only_left_radius{ - border-top-right-radius:0; - border-bottom-right-radius:0; - border-top-left-radius:2em; - border-bottom-left-radius:2em; +.only_left_radius { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: 2em; + border-bottom-left-radius: 2em; } -.no_radius{ - border-radius:0; +.no_radius { + border-radius: 0; } -.clear_survey_filter{ - cursor:pointer; +.clear_survey_filter, .filter-all, .filter-finished{ + cursor: pointer; } .nvtooltip h5 { - margin: 0; - line-height: 18px; - font-weight: bold; - background-color: rgba(247,247,247,0.75); - text-align: center; - border-bottom: 1px solid #ebebeb; - -webkit-border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - border-radius: 5px 5px 0 0; + margin: 0; + line-height: 18px; + font-weight: bold; + background-color: rgba(247,247,247,0.75); + text-align: center; + border-bottom: 1px solid #ebebeb; + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; } -.survey_answer i{ - padding:3px; - cursor:pointer; +.survey_answer i { + padding:3px; + cursor:pointer; } -.survey_answer i.invisible{ - visibility: hidden!important; + +.survey_answer i.invisible { + visibility: hidden!important; +} + +@media print { + .tab-content > .tab-pane { + display: block; + } + + .tab-content > .survey_graph > svg { + width: 1150px; + } } diff --git a/addons/survey/static/src/js/survey_result.js b/addons/survey/static/src/js/survey_result.js index 54a6d248f95..7c9b935582a 100644 --- a/addons/survey/static/src/js/survey_result.js +++ b/addons/survey/static/src/js/survey_result.js @@ -62,29 +62,38 @@ $(document).ready(function () { //initialize discreteBar Chart function init_bar_chart(){ return nv.models.discreteBarChart() - .x(function(d) { return d.text; }) - .y(function(d) { return d.count; }) - .staggerLabels(true) - .tooltips(false) - .showValues(true); + .x(function(d) { return d.text; }) + .y(function(d) { return d.count; }) + .staggerLabels(true) + .tooltips(false) + .showValues(true); + } + + //initialize Pie Chart + function init_pie_chart(){ + return nv.models.pieChart() + .x(function(d) { return d.text; }) + .y(function(d) { return d.count; }) + .showLabels(false); } //load chart to svg element chart:initialized chart, response:AJAX response, quistion_id:if of survey question, tick_limit:text length limit - function load_chart(chart, response, question_id, tick_limit){ + function load_chart(chart, response, question_id, tick_limit, graph_type){ // Custom Tick fuction for replacing long text with '...' var customtick_function = function(d){ if(! this || d.length <= tick_limit){ return d; } else{ - return d.slice(0,tick_limit)+'...'; + return d.slice(0,tick_limit) + '...'; } }; - chart.xAxis - .tickFormat(customtick_function); - chart.yAxis - .tickFormat(d3.format('d')); - + if (graph_type != 'pie'){ + chart.xAxis + .tickFormat(customtick_function); + chart.yAxis + .tickFormat(d3.format('d')); + } d3.select('#graph_question_' + question_id + ' svg') .datum(response) .transition().duration(500).call(chart); @@ -96,42 +105,42 @@ $(document).ready(function () { $.each(survey_graphs, function(index, graph){ var question_id = $(graph).attr("data-question_id"); var graph_type = $(graph).attr("data-graph_type"); - var current_filters = $(graph).attr("data-current_filters"); - console.log(current_filters); - $.ajax({ - url: '/survey/results/graph/'+question_id, - type: 'POST', - dataType: 'json', - data:{'current_filters': current_filters}, - success: function(response, status, xhr, wfe){ - if(graph_type == 'multi_bar'){ - nv.addGraph(function(){ - var chart = init_multibar_chart(); - return load_chart(chart,response,question_id,25); - }); - } - else if(graph_type == 'bar'){ - nv.addGraph(function() { - var chart = init_bar_chart(); - return load_chart(chart,response,question_id,35); - }); - } - } - }); + var graph_data = JSON.parse($(graph).attr("graph-data")); + if(graph_type == 'multi_bar'){ + nv.addGraph(function(){ + var chart = init_multibar_chart(); + return load_chart(chart, graph_data, question_id, 25); + }); + } + else if(graph_type == 'bar'){ + nv.addGraph(function() { + var chart = init_bar_chart(); + return load_chart(chart, graph_data, question_id, 35); + }); + } + else if(graph_type == 'pie'){ + nv.addGraph(function() { + var chart = init_pie_chart(); + return load_chart(chart, graph_data, question_id, 25, 'pie'); + }); + } }); // Script for filter - $('td.survey_answer').hover(function(){$(this).find('i.fa-filter').removeClass('invisible');},function(){$(this).find('i.fa-filter').addClass('invisible');}); + $('td.survey_answer').hover(function(){ + $(this).find('i.fa-filter').removeClass('invisible'); + }, function(){ + $(this).find('i.fa-filter').addClass('invisible'); + }); $('td.survey_answer i.fa-filter').click(function(){ - var cell=$(this); - var question_id = cell.attr('data-question_id'); + var cell = $(this); var row_id = cell.attr('data-row_id') | 0; var answer_id = cell.attr('data-answer_id'); if(document.URL.indexOf("?") == -1){ - window.location.href = document.URL+'?' + encodeURI(row_id + ','+answer_id + '=' + question_id); + window.location.href = document.URL + '?' + encodeURI(row_id + ',' + answer_id); } - else{ - window.location.href = document.URL+'&' + encodeURI(row_id + ','+answer_id + '=' + question_id); + else { + window.location.href = document.URL + '&' + encodeURI(row_id + ',' + answer_id); } }); @@ -139,6 +148,30 @@ $(document).ready(function () { $('.clear_survey_filter').click(function(){ window.location.href = document.URL.substring(0,document.URL.indexOf("?")); }); + $('span.filter-all').click(function(){ + event.preventDefault(); + if(document.URL.indexOf("finished") != -1){ + window.location.href = document.URL.replace('?finished&','?').replace('&finished&','&').replace('?finished','').replace('&finished',''); + } + }).hover(function(){ + if(document.URL.indexOf("finished") == -1){ + $(this)[0].style.cursor = 'default'; + } + }); + // toggle finished/all surveys filter + $('span.filter-finished').click(function(){ + event.preventDefault(); + if(document.URL.indexOf("?") == -1){ + window.location.href = document.URL + '?' + encodeURI('finished'); + } + else if(document.URL.indexOf("finished") == -1){ + window.location.href = document.URL + '&' + encodeURI('finished'); + } + }).hover(function(){ + if(document.URL.indexOf("finished") != -1){ + $(this)[0].style.cursor = 'default'; + } + }); console.debug("[survey] Survey Result JS loaded!"); }); \ No newline at end of file diff --git a/addons/survey/survey.py b/addons/survey/survey.py index 03644768bac..e16c4949b8f 100644 --- a/addons/survey/survey.py +++ b/addons/survey/survey.py @@ -24,6 +24,8 @@ from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DF from openerp.addons.website.models.website import slug from urlparse import urljoin +from itertools import product +from collections import Counter import datetime import logging @@ -133,6 +135,126 @@ class survey_survey(osv.Model): res[survey.id] = urljoin(base_url, "survey/results/%s" % slug(survey)) return res + def filter_input_ids(self, cr, uid, filters, finished=False, context=None): + '''If user applies any filters, then this function returns list of + filtered user_input_id and label's strings for display data in web. + :param filters: list of dictionary (having: row_id, ansewr_id) + :param finished: True for completely filled survey,Falser otherwise. + :returns list of filtered user_input_ids. + ''' + if context is None: + context = {} + if filters: + input_line_obj = self.pool.get('survey.user_input_line') + domain_filter, choice, filter_display_data = [], [], [] + for filter in filters: + row_id, answer_id = filter['row_id'], filter['answer_id'] + if row_id == 0: + choice.append(answer_id) + else: + domain_filter.extend(['|', ('value_suggested_row.id', '=', row_id), ('value_suggested.id', '=', answer_id)]) + if choice: + domain_filter.insert(0, ('value_suggested.id', 'in', choice)) + else: + domain_filter = domain_filter[1:] + line_ids = input_line_obj.search(cr, uid, domain_filter, context=context) + filtered_input_ids = [input.user_input_id.id for input in input_line_obj.browse(cr, uid, line_ids, context=context)] + else: + filtered_input_ids, filter_display_data = [], [] + if finished: + user_input = self.pool.get('survey.user_input') + if not filtered_input_ids: + current_filters = user_input.search(cr, uid, [], context=context) + user_input_objs = user_input.browse(cr, uid, current_filters, context=context) + else: + user_input_objs = user_input.browse(cr, uid, filtered_input_ids, context=context) + return [input.id for input in user_input_objs if input.state == 'done'] + return filtered_input_ids + + def get_filter_display_data(self, cr, uid, filters, context): + '''Returns data to display current filters + :param filters: list of dictionary (having: row_id, answer_id) + :param finished: True for completely filled survey, False otherwise. + :returns list of dict having data to display filters. + ''' + filter_display_data = [] + if filters: + question_obj = self.pool.get('survey.question') + label_obj = self.pool.get('survey.label') + for filter in filters: + row_id, answer_id = filter['row_id'], filter['answer_id'] + question_id = label_obj.browse(cr, uid, answer_id, context=context).question_id.id + question = question_obj.browse(cr, uid, question_id, context=context) + if row_id == 0: + labels = label_obj.browse(cr, uid, [answer_id], context=context) + else: + labels = label_obj.browse(cr, uid, [row_id, answer_id], context=context) + filter_display_data.append({'question_text': question.question, 'labels': [label.value for label in labels]}) + return filter_display_data + + def prepare_result(self, cr, uid, question, current_filters=[], context=None): + ''' Compute statistical data for questions by counting number of vote per choice on basis of filter ''' + if context is None: + context = {} + #Calculate and return statistics for choice + if question.type in ['simple_choice', 'multiple_choice']: + result_summary = {} + [result_summary.update({label.id: {'text': label.value, 'count': 0, 'answer_id': label.id}}) for label in question.labels_ids] + for input_line in question.user_input_line_ids: + if result_summary.get(input_line.value_suggested.id) and (not(current_filters) or input_line.user_input_id.id in current_filters): + result_summary[input_line.value_suggested.id]['count'] += 1 + result_summary = result_summary.values() + + #Calculate and return statistics for matrix + if question.type == 'matrix': + rows, answers, res = {}, {}, {} + [rows.update({label.id: label.value}) for label in question.labels_ids_2] + [answers.update({label.id: label.value}) for label in question.labels_ids] + for cell in product(rows.keys(), answers.keys()): + res[cell] = 0 + for input_line in question.user_input_line_ids: + if not(current_filters) or input_line.user_input_id.id in current_filters: + res[(input_line.value_suggested_row.id, input_line.value_suggested.id)] += 1 + result_summary = {'answers': answers, 'rows': rows, 'result': res} + + #Calculate and return statistics for free_text, textbox, datetime + if question.type in ['free_text', 'textbox', 'datetime']: + result_summary = [] + for input_line in question.user_input_line_ids: + if not(current_filters) or input_line.user_input_id.id in current_filters: + result_summary.append(input_line) + + #Calculate and return statistics for numerical_box + if question.type == 'numerical_box': + result_summary = {'input_lines': []} + all_inputs = [] + for input_line in question.user_input_line_ids: + if not(current_filters) or input_line.user_input_id.id in current_filters: + all_inputs.append(input_line.value_number) + result_summary['input_lines'].append(input_line) + if all_inputs: + result_summary.update({'average': round(sum(all_inputs) / len(all_inputs), 2), + 'max': round(max(all_inputs), 2), + 'min': round(min(all_inputs), 2), + 'most_comman': Counter(all_inputs).most_common(5)}) + return result_summary + + def get_input_summary(self, cr, uid, question, current_filters=[], context=None): + ''' Returns overall summary of question e.g. answered, skipped, total_inputs on basis of filter ''' + if context is None: + context = {} + result = {} + if question.survey_id.user_input_ids: + total_input_ids = current_filters or [input_id.id for input_id in question.survey_id.user_input_ids if input_id.state != 'new'] + result['total_inputs'] = len(total_input_ids) + question_input_ids = [] + for user_input in question.user_input_line_ids: + if not user_input.skipped: + question_input_ids.append(user_input.user_input_id.id) + result['answered'] = len(set(question_input_ids) & set(total_input_ids)) + result['skipped'] = result['total_inputs'] - result['answered'] + return result + # Model fields # _columns = { diff --git a/addons/survey/views/survey_result.xml b/addons/survey/views/survey_result.xml index a5113df25e3..fdc8d8b9ccf 100644 --- a/addons/survey/views/survey_result.xml +++ b/addons/survey/views/survey_result.xml @@ -13,42 +13,66 @@
+

-
-

Filters Clear All Filters

- - - +
+
Filters Clear All Filters
+
+ + All surveysFinished surveys + + + All surveysFinished surveys + + + + +
-
+
+


-
- +
+ + + +

Question + + Matrix: + + + + Answered Skipped

- -
+ + +
+
+ + + + + + + + + + + +
- - - - - - - - - - - + +

Sorry, No one answered this question.

@@ -59,78 +83,70 @@