[MERGE] Reporting tool for surveys + [REF] Serveral refactorings (code style, missing semicolons in JS, typos...) + [FIX] Security breach using unsafe eval
bzr revid: rim@openerp.com-20140303130544-xoage8hgkhtfhwpg
This commit is contained in:
@@ -36,12 +36,13 @@ sent mails with personal token for the invitation of the survey.
|
||||
'summary': 'Create surveys, collect answers and print statistics',
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'https://www.openerp.com/apps/survey/',
|
||||
'depends': ['email_template', 'mail', 'website', 'web_kanban_gauge'],
|
||||
'depends': ['email_template', 'mail', 'website', 'web_kanban_gauge', 'web_graph'],
|
||||
'data': [
|
||||
'security/survey_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/survey_views.xml',
|
||||
'views/survey_templates.xml',
|
||||
'views/survey_result.xml',
|
||||
'wizard/survey_email_compose_message.xml',
|
||||
'data/survey_cron.xml'
|
||||
],
|
||||
|
||||
@@ -19,16 +19,19 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DTF
|
||||
from datetime import datetime
|
||||
import werkzeug
|
||||
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
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.addons.website.controllers.main import Website as controllers
|
||||
controllers = controllers()
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DTF
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -280,6 +283,144 @@ class WebsiteSurvey(http.Controller):
|
||||
'token': token,
|
||||
'page_nr': 0})
|
||||
|
||||
@http.route(['/survey/results/<model("survey.survey"):survey>'],
|
||||
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', [], []
|
||||
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)
|
||||
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,
|
||||
'page_range': self.page_range,
|
||||
'current_filters': current_filters,
|
||||
'filter_display_data': filter_display_data
|
||||
})
|
||||
|
||||
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 = [], [], []
|
||||
|
||||
#if user add some random data in query URI
|
||||
try:
|
||||
for ids in filters:
|
||||
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)
|
||||
|
||||
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/<model("survey.question"):question>'],
|
||||
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', '[]'))
|
||||
result = []
|
||||
if question.type in ['simple_choice', 'multiple_choice']:
|
||||
result.append({'key': str(question.question),
|
||||
'values': self.prepare_result(question, current_filters)})
|
||||
if question.type == 'matrix':
|
||||
data = self.prepare_result(question, current_filters)
|
||||
for answer in data['answers']:
|
||||
values = []
|
||||
for res in data['result']:
|
||||
if res[1] == answer:
|
||||
values.append({'text': data['rows'][res[0]], 'count': data['result'][res]})
|
||||
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 <key>: <value> into the <dictionary>. If <key> is
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.no_radius{
|
||||
border-radius:0;
|
||||
}
|
||||
|
||||
.clear_survey_filter{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
.survey_answer i{
|
||||
padding:3px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.survey_answer i.invisible{
|
||||
visibility: hidden!important;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* OpenERP, Open Source Management Solution
|
||||
* Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
$(document).ready(function () {
|
||||
'use strict';
|
||||
console.debug("[survey] Survey Result JS is loading...");
|
||||
|
||||
//Script For Pagination
|
||||
var survey_pagination = $('.pagination');
|
||||
$.each(survey_pagination, function(index, pagination){
|
||||
var question_id = $(pagination).attr("data-question_id");
|
||||
var limit = $(pagination).attr("data-record_limit"); //Number of Record Par Page. If you want to change number of record per page, change record_limit in pagination template.
|
||||
$('#table_question_'+ question_id +' tbody tr:lt('+limit+')').removeClass('hidden');
|
||||
$('#pagination_'+question_id+' li:first').addClass('active');
|
||||
$('#pagination_'+question_id+' li a').click(function(event){
|
||||
event.preventDefault();
|
||||
$('#pagination_'+question_id+' li').removeClass('active');
|
||||
$(this).parent('li').addClass('active');
|
||||
$('#table_question_'+ question_id +' tbody tr').addClass('hidden');
|
||||
var num = $(this).text();
|
||||
var min = (limit * (num-1))-1;
|
||||
if (min == -1){
|
||||
$('#table_question_'+ question_id +' tbody tr:lt('+ limit * num +')').removeClass('hidden');
|
||||
}
|
||||
else{
|
||||
$('#table_question_'+question_id+' tbody tr:lt('+ limit * num +'):gt('+min+')').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//initialize MultiBar Chart
|
||||
function init_multibar_chart(){
|
||||
var chart = nv.models.multiBarChart()
|
||||
.x(function(d) { return d.text; })
|
||||
.y(function(d) { return d.count; })
|
||||
.staggerLabels(true);
|
||||
|
||||
// Replacing Library's Default Tooltip with our Custom One
|
||||
chart.tooltip(function(key, x, y, e, graph) {
|
||||
return '<h5 class="panel-primary"><div class="panel-heading">' + x + '</div></h5>' +
|
||||
'<p>' + '<b>Responses : </b>' + key + '</p>' +
|
||||
'<p>' + "<b>Total Vote : </b>" + y + '</p>';
|
||||
});
|
||||
return chart;
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
//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){
|
||||
// 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)+'...';
|
||||
}
|
||||
};
|
||||
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);
|
||||
nv.utils.windowResize(chart.update);
|
||||
return chart;
|
||||
}
|
||||
//Script For Graph
|
||||
var survey_graphs = $('.survey_graph');
|
||||
$.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', // TODO check that something is posted
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 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 i.fa-filter').click(function(){
|
||||
var cell=$(this);
|
||||
var question_id = cell.attr('data-question_id');
|
||||
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);
|
||||
}
|
||||
else{
|
||||
window.location.href = document.URL+'&' + encodeURI(row_id + ','+answer_id + '=' + question_id);
|
||||
}
|
||||
});
|
||||
|
||||
// for clear all filters
|
||||
$('.clear_survey_filter').click(function(){
|
||||
window.location.href = document.URL.substring(0,document.URL.indexOf("?"));
|
||||
});
|
||||
|
||||
console.debug("[survey] Survey Result JS loaded!");
|
||||
});
|
||||
@@ -104,7 +104,7 @@ class survey_survey(osv.Model):
|
||||
""" Computes an URL for the survey results """
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid,
|
||||
'web.base.url')
|
||||
return {id: urljoin(base_url, "survey/result/%s/" % id) for id in ids}
|
||||
return {id: urljoin(base_url, "survey/results/%s/" % id) for id in ids}
|
||||
|
||||
# Model fields #
|
||||
|
||||
@@ -479,6 +479,9 @@ class survey_question(osv.Model):
|
||||
oldname='minimum_req_ans'),
|
||||
'constr_error_msg': fields.char("Error message",
|
||||
oldname='req_error_msg'),
|
||||
'user_input_line_ids': fields.one2many('survey.user_input_line',
|
||||
'question_id', 'Answers',
|
||||
domain=[('skipped', '=', False)]),
|
||||
}
|
||||
_defaults = {
|
||||
'page_id': lambda s, cr, uid, c: c.get('page_id'),
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="result" name="Survey Result">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="head">
|
||||
<link href="/web_graph/static/src/css/nv.d3.css" rel="stylesheet" type="text/css"></link>
|
||||
<link href="/survey/static/src/css/survey_result.css" rel="stylesheet" type="text/css"></link>
|
||||
<script src="/web_graph/static/lib/nvd3/d3.v3.js"></script>
|
||||
<script src="/web_graph/static/lib/nvd3/nv.d3.js"></script>
|
||||
<script type="text/javascript" src="/survey/static/src/js/survey_result.js" />
|
||||
</t>
|
||||
<div class="oe_structure" />
|
||||
<div class="container">
|
||||
<div class="jumbotron mt32">
|
||||
<h1><span t-field="survey.title"></span> <span style="font-size:1.5em;" class="fa fa-bar-chart-o pull-right "/></h1>
|
||||
<h2><span t-field="survey.description"></span></h2>
|
||||
</div>
|
||||
<div t-if="filter_display_data" class="filteres">
|
||||
<span><h3><span class="fa fa-filter"></span> Filters <small class="pull-right clear_survey_filter"> <i class="fa fa-times"></i> Clear All Filters</small></h3></span>
|
||||
<span t-foreach="filter_display_data" t-as="filter_data">
|
||||
<span class="label label-primary only_left_radius"><i class="fa fa-filter"></i></span><span class="label label-primary no_radius" t-esc="filter_data['question_text']"></span><span class="label label-success only_right_radius" t-esc="' > '.join(filter_data['labels'])"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div t-foreach='survey.page_ids' t-as='page'>
|
||||
<h1 class="mt16" t-field='page.title'></h1>
|
||||
<hr/>
|
||||
<div t-foreach='page.question_ids' t-as='question' class="mt16">
|
||||
<t t-set="input_summary" t-value="get_input_summary(question, current_filters)"/>
|
||||
<h4>
|
||||
<b>Question </b>
|
||||
<span t-field='question.question'></span>
|
||||
<span class="pull-right">
|
||||
<span class="label label-success"><span t-esc="input_summary['answered']"></span> Answered</span>
|
||||
<span class="label label-danger"><span t-esc="input_summary['skipped']"></span> Skipped</span>
|
||||
</span>
|
||||
</h4>
|
||||
<t t-if="question.description">
|
||||
<h5><span class="text-muted" t-field="question.description"></span></h5>
|
||||
</t>
|
||||
<t t-if="question.type in ['textbox', 'free_text', 'datetime']">
|
||||
<t t-call="survey.result_text"></t>
|
||||
</t>
|
||||
<t t-if="question.type in ['simple_choice', 'multiple_choice']">
|
||||
<t t-call="survey.result_choice"></t>
|
||||
</t>
|
||||
<t t-if="question.type == 'matrix'">
|
||||
<t t-call="survey.result_matrix"></t>
|
||||
</t>
|
||||
<t t-if="question.type == 'numerical_box'">
|
||||
<t t-call="survey.result_number"></t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure" />
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Result for free_text,textbox and datetime -->
|
||||
<template id="result_text" name="Text Result">
|
||||
<t t-if="input_summary['answered'] != 0">
|
||||
<table class="table table-hover" t-att-id="'table_question_'+str(question.id)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>User Responses</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-set="text_result" t-value="prepare_result(question, current_filters)"></t>
|
||||
<tr class="hidden" t-foreach="text_result" t-as="user_input">
|
||||
<td><t t-esc="user_input_index+1"></t></td>
|
||||
<t t-if="question.type == 'free_text'">
|
||||
<td>
|
||||
<span t-field="user_input.value_free_text"></span><br/>
|
||||
<small><p t-field="user_input.date_create" class="fa fa-calendar oe_date text-muted"></p></small>
|
||||
<span class="pull-right">
|
||||
<a t-att-href="user_input.user_input_id.print_url+user_input.user_input_id.token"> <i class="fa fa-print"></i> Print respondent's answers</a><br/>
|
||||
</span>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="question.type == 'textbox'">
|
||||
<td>
|
||||
<span t-field="user_input.value_text"></span><br/>
|
||||
<small><p t-field="user_input.date_create" class="fa fa-calendar oe_date text-muted"></p>
|
||||
<span class="pull-right">
|
||||
<a t-att-href="user_input.user_input_id.print_url+user_input.user_input_id.token"> <i class="fa fa-print"></i> Print respondent's answers</a><br/>
|
||||
</span>
|
||||
</small>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="question.type == 'datetime'">
|
||||
<td>
|
||||
<span class="oe_date" t-field="user_input.value_date"></span><br/>
|
||||
<span class="pull-right">
|
||||
<a t-att-href="user_input.user_input_id.print_url+user_input.user_input_id.token"> <i class="fa fa-print"></i> Print respondent's answers</a><br/>
|
||||
</span>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t t-call="survey.pagination" />
|
||||
</t>
|
||||
<t t-if="input_summary['answered'] == 0">
|
||||
<h2 style="padding-top:30px;padding-bottom:30px;text-align:center;" class="text-muted">Sorry, No one answered this question.</h2>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Result for simple_choice and multiple_choice -->
|
||||
<template id="result_choice" name="Choice Result">
|
||||
<div>
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a t-att-href="'#graph_question_'+str(question.id)" data-toggle="tab">
|
||||
<i class="fa fa-bar-chart-o"></i> Graph
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a t-att-href="'#data_question_'+str(question.id)" data-toggle="tab">
|
||||
<i class="fa fa-list-alt"></i> Data
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active survey_graph" t-att-id="'graph_question_'+str(question.id)" t-att-data-question_id="question.id" data-graph_type="bar" t-att-data-current_filters="current_filters">
|
||||
<!-- svg element for drawing bar chart -->
|
||||
<svg style="height:20em"></svg>
|
||||
</div>
|
||||
<div class="tab-pane" t-att-id="'data_question_'+str(question.id)">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Answer Choices</th>
|
||||
<th>User Responses</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="prepare_result(question, current_filters)" t-as="user_input">
|
||||
<td>
|
||||
<p t-esc="user_input['text']"></p>
|
||||
</td>
|
||||
<td class="survey_answer">
|
||||
<span t-esc="round(user_input['count']*100.0/(input_summary['answered'] or 1),2)"></span> %
|
||||
<span t-esc="user_input['count']" class="label label-primary">Vote</span>
|
||||
<i class="fa fa-filter text-primary invisible survey_filter" t-att-data-question_id="question.id" t-att-data-answer_id="user_input['answer_id']"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Result for matrix -->
|
||||
<template id="result_matrix" name="Matrix Result">
|
||||
<t t-set="matrix_result" t-value="prepare_result(question, current_filters)" />
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a t-att-href="'#graph_question_'+str(question.id)" data-toggle="tab">
|
||||
<i class="fa fa-bar-chart-o"></i>
|
||||
Graph
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a t-att-href="'#data_question_'+str(question.id)" data-toggle="tab">
|
||||
<i class="fa fa-list-alt"></i>
|
||||
Data
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active with-3d-shadow with-transitions survey_graph" t-att-id="'graph_question_'+str(question.id)" t-att-data-question_id= "question.id" data-graph_type= "multi_bar" t-att-data-current_filters="current_filters">
|
||||
<!-- svg element for drawing Multibar chart -->
|
||||
<svg style="height:20em"></svg>
|
||||
</div>
|
||||
<div class="tab-pane" t-att-id="'data_question_'+str(question.id)">
|
||||
<table class="table table-hover text-right">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-right" t-foreach="matrix_result['answers']" t-as="answer_id">
|
||||
<span t-esc="matrix_result['answers'][answer_id]"></span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="matrix_result['rows']" t-as="row_id">
|
||||
<td>
|
||||
<span t-esc="matrix_result['rows'][row_id]"></span>
|
||||
</td>
|
||||
<td class="survey_answer" t-foreach="matrix_result['answers']" t-as="answer_id">
|
||||
<span t-esc="round(matrix_result['result'][(row_id,answer_id)]*100.0/(input_summary['answered'] or 1),2)"></span> %
|
||||
<span class="label label-primary" t-esc="matrix_result['result'][(row_id,answer_id)]"></span><i class="fa fa-filter text-primary invisible survey_filter" t-att-data-question_id="question.id" t-att-data-row_id="row_id" t-att-data-answer_id="answer_id"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Result for Numeric Data -->
|
||||
<template id="result_number" name="Number Result">
|
||||
<t t-set="number_result" t-value="prepare_result(question, current_filters)" />
|
||||
<t t-set="text_result" t-value="number_result['input_lines']" />
|
||||
<span class="pull-right mt8">
|
||||
<span class="label label-default only_left_radius">Maximum </span> <span class="label label-success only_right_radius" t-esc="number_result['max']"></span>
|
||||
<span class="label label-default only_left_radius">Minimum </span> <span class="label label-danger only_right_radius" t-esc="number_result['min']"></span>
|
||||
<span class="label label-default only_left_radius">Average </span> <span class="label label-warning only_right_radius" t-esc="number_result['average']"></span>
|
||||
</span>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a t-att-href="'#most_common_'+str(question.id)" data-toggle="tab">
|
||||
<i class="fa fa-list-ol"></i>
|
||||
Most Common <span t-esc="len(number_result['most_comman'])"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a t-att-href="'#data_question_'+str(question.id)" data-toggle="tab">
|
||||
<i class="fa fa-list-alt"></i>
|
||||
All Data
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active with-3d-shadow with-transitions" t-att-id="'most_common_'+str(question.id)">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User Responce</th>
|
||||
<th>Occurence</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="number_result['most_comman']" t-as="row">
|
||||
<td>
|
||||
<span t-esc="row[0]"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span t-esc="row[1]"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="tab-pane" t-att-id="'data_question_'+str(question.id)">
|
||||
<table class="table table-hover" t-att-id="'table_question_'+str(question.id)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>User Responses</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="hidden" t-foreach="number_result['input_lines']" t-as="user_input">
|
||||
<td><t t-esc="user_input_index+1"></t></td>
|
||||
<td>
|
||||
<span t-field="user_input.value_number"></span><br/>
|
||||
<small><p t-field="user_input.date_create" class="fa fa-calendar oe_date text-muted"></p></small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t t-call="survey.pagination"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template id="no_result" name="Survey Result">
|
||||
<t t-call="website.layout">
|
||||
<div class="container">
|
||||
<h2><span t-field='survey.title'></span></h2>
|
||||
<div class="jumbotron mt32">
|
||||
<h2 class="text-muted">
|
||||
<span>
|
||||
<i style="font-size:1.8em" class="fa fa-users pull-right"></i>
|
||||
</span>
|
||||
Sorry, No one answered this survey yet
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
<!-- Pagination Element -->
|
||||
<template id="pagination" name="Survey Result">
|
||||
<t t-set="record_limit" t-value="5"/><!-- Change This record_limit to change number of record per page-->
|
||||
<ul t-att-id="'pagination_%d' % question.id" class="pagination" t-att-data-question_id="question.id" t-att-data-record_limit="record_limit">
|
||||
<t t-if="len(text_result) > record_limit">
|
||||
<li t-foreach="page_range(len(text_result), record_limit)" t-as="num">
|
||||
<a href="#" t-esc="num"></a>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
Reference in New Issue
Block a user