[IMP] Server side validation
bzr revid: rim@openerp.com-20131115151435-ht5eg6e6vfiydibe
This commit is contained in:
@@ -38,8 +38,8 @@ sent mails with user name and password for the invitation of the survey.
|
||||
'website': 'https://www.openerp.com/',
|
||||
'depends': ['email_template', 'mail', 'website'],
|
||||
'data': [
|
||||
#'security/survey_security.xml',
|
||||
#'security/ir.model.access.csv',
|
||||
'security/survey_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'survey_view.xml',
|
||||
'views/survey_templates.xml',
|
||||
#'wizard/survey_answer.xml',
|
||||
|
||||
@@ -24,7 +24,9 @@ from openerp.addons.web.http import request
|
||||
from openerp.addons.website.models import website
|
||||
|
||||
import werkzeug
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -57,7 +59,7 @@ class WebsiteSurvey(http.Controller):
|
||||
if survey_obj.exists(cr, uid, survey.id, context=context) == []:
|
||||
return werkzeug.utils.redirect("/survey/")
|
||||
|
||||
_logger.debug('Post request data: %s', post)
|
||||
_logger.debug('Post data: %s', post)
|
||||
|
||||
# Store answer data
|
||||
# (/!\ assumes JavaScript validation of answers has succeeded!)
|
||||
@@ -96,14 +98,25 @@ class WebsiteSurvey(http.Controller):
|
||||
# return {'valid': True, 'errors': None}
|
||||
|
||||
@website.route(['/survey/submit/<model("survey.survey"):survey>'],
|
||||
type='json', auth='public', multilang=True)
|
||||
def submit(self, survey=None, **post):
|
||||
_logger.debug("Incoming json data: " + post.__str__())
|
||||
type='http', auth='public', multilang=True)
|
||||
def submit(self, survey, **post):
|
||||
_logger.debug("Incoming data: " + post.__str__())
|
||||
page_nr = int(post['current'])
|
||||
questions = survey.page_ids[page_nr].question_ids
|
||||
|
||||
# transform post data into dictionary
|
||||
# for each {K:V} in post, check
|
||||
errors = {}
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s_%s" % (survey.id, page_nr, question.id)
|
||||
errors.update(self.validate_question(survey.id, page_nr, question, post, answer_tag))
|
||||
|
||||
return {'redirect': '/survey/'}
|
||||
ret = {}
|
||||
if (len(errors) != 0):
|
||||
ret['errors'] = errors
|
||||
else:
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
# Store here data if allowed
|
||||
ret['redirect'] = "nexturl"
|
||||
return json.dumps(ret)
|
||||
|
||||
# Printing routes
|
||||
@website.route(['/survey/print/<model("survey.survey"):survey>/'],
|
||||
@@ -114,3 +127,130 @@ class WebsiteSurvey(http.Controller):
|
||||
return request.website.render('survey.survey_print',
|
||||
{'survey': survey,
|
||||
'pagination': pagination})
|
||||
|
||||
# Validation methods
|
||||
|
||||
def validate_question(self, survey_id, page_nr, question, post, answer_tag):
|
||||
''' Routing to the right question valider, depending on question type '''
|
||||
try:
|
||||
checker = getattr(self, 'validate_' + question.type)
|
||||
except AttributeError:
|
||||
_logger.warning(question.type + ": This type of question has no validation method")
|
||||
return {}
|
||||
else:
|
||||
return checker(survey_id, page_nr, question, post, answer_tag)
|
||||
|
||||
def validate_free_text(self, survey_id, page_nr, question, post, answer_tag):
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if question.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: question.constr_error_msg})
|
||||
return errors
|
||||
|
||||
def validate_textbox(self, survey_id, page_nr, question, post, answer_tag):
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if question.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: question.constr_error_msg})
|
||||
# Answer validation (if properly defined)
|
||||
if answer and question.validation_required and question.validation_type:
|
||||
# Length of the answer must be in a range
|
||||
if question.validation_type == "has_length":
|
||||
if not (question.validation_length_min <= len(answer) <= question.validation_length_max):
|
||||
errors.update({answer_tag: question.validation_error_msg})
|
||||
|
||||
# Answer must be an integer in a particular range
|
||||
elif question.validation_type == "is_integer":
|
||||
try:
|
||||
intanswer = int(answer)
|
||||
# Answer is not an integer
|
||||
except ValueError:
|
||||
errors.update({answer_tag: question.validation_error_msg})
|
||||
else:
|
||||
# Answer is not in the right range
|
||||
if not (question.validation_min_int_value <= intanswer <= question.validation_max_int_value):
|
||||
errors.update({answer_tag: question.validation_error_msg})
|
||||
# Answer must be a float in a particular range
|
||||
elif question.validation_type == "is_decimal":
|
||||
try:
|
||||
floatanswer = float(answer)
|
||||
# Answer is not an integer
|
||||
except ValueError:
|
||||
errors.update({answer_tag: question.validation_error_msg})
|
||||
else:
|
||||
# Answer is not in the right range
|
||||
if not (question.validation_min_float_value <= floatanswer <= question.validation_max_float_value):
|
||||
errors.update({answer_tag: question.validation_error_msg})
|
||||
|
||||
# Answer must be a date in a particular range
|
||||
elif question.validation_type == "is_date":
|
||||
raise Exception("Not implemented")
|
||||
# Answer must be an email address
|
||||
# Note: this validation is very basic:
|
||||
# all the strings of the form
|
||||
# <something>@<anything>.<extension>
|
||||
# will be accepted
|
||||
elif question.validation_type == "is_email":
|
||||
if not re.match(r"[^@]+@[^@]+\.[^@]+", answer):
|
||||
errors.update({answer_tag: question.validation_error_msg})
|
||||
else:
|
||||
pass
|
||||
return errors
|
||||
|
||||
def validate_numerical_box(self, survey_id, page_nr, question, post, answer_tag):
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if question.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: question.constr_error_msg})
|
||||
# Checks if user input is a number
|
||||
if answer:
|
||||
try:
|
||||
float(answer)
|
||||
except ValueError:
|
||||
errors.update({answer_tag: question.constr_error_msg})
|
||||
return errors
|
||||
|
||||
def validate_datetime(self, survey_id, page_nr, question, post, answer_tag):
|
||||
errors = {}
|
||||
answer = post[answer_tag].strip()
|
||||
# Empty answer to mandatory question
|
||||
if question.constr_mandatory and not answer:
|
||||
errors.update({answer_tag: question.constr_error_msg})
|
||||
# Checks if user input is a datetime
|
||||
# TODO when datepicker will be available
|
||||
return errors
|
||||
|
||||
# def validate_simple_choice_scale(self, survey_id, page_nr, question, post, answer_tag):
|
||||
# answer_tag = survey_id.__str__() + '*' + page_nr + '*' + question.id.__str__()
|
||||
# problems = []
|
||||
# if question.constr_mandatory:
|
||||
# problems = problems + self.__has_empty_input(question, post, answer_tag)
|
||||
# return problems
|
||||
|
||||
# def validate_simple_choice_dropdown(self, survey_id, page_nr, question, post, answer_tag):
|
||||
# answer_tag = survey_id.__str__() + '*' + page_nr + '*' + question.id.__str__()
|
||||
# problems = []
|
||||
# if question.constr_mandatory:
|
||||
# problems = problems + self.__has_empty_input(question, post, answer_tag)
|
||||
# return problems
|
||||
|
||||
# def validate_multiple_choice(self, survey_id, page_nr, question, post, answer_tag):
|
||||
# answer_tag = survey_id.__str__() + '*' + page_nr + '*' + question.id.__str__()
|
||||
# problems = []
|
||||
# return problems
|
||||
|
||||
# def validate_matrix(self, survey_id, page_nr, question, post, answer_tag):
|
||||
# answer_tag = survey_id.__str__() + '*' + page_nr + '*' + question.id.__str__()
|
||||
# problems = []
|
||||
# return problems
|
||||
|
||||
def dict_keys_startswith(self, dictionary, string):
|
||||
'''Returns a dictionary containing the elements of <dict> whose keys start
|
||||
with <string>.
|
||||
|
||||
.. note::
|
||||
This function uses dictionary comprehensions (Python >= 2.7)'''
|
||||
return {k: dictionary[k] for k in filter(lambda key: key.startswith(string), dictionary.keys())}
|
||||
|
||||
@@ -3,6 +3,6 @@ access_survey_user,survey.user,model_survey_survey,base.group_survey_user,1,0,0,
|
||||
access_survey_manager,survey.manager,model_survey_survey,base.group_survey_manager,1,1,1,1
|
||||
access_survey_page_manager,survey.page manager,model_survey_page,base.group_survey_manager,1,1,1,1
|
||||
access_survey_question_manager,survey.question manager,model_survey_question,base.group_survey_manager,1,1,1,1
|
||||
access_survey_suggestion_manager,survey.suggestion manager,model_survey_suggestion,base.group_survey_manager,1,1,1,1
|
||||
access_survey_label_manager,survey.label manager,model_survey_label,base.group_survey_manager,1,1,1,1
|
||||
access_survey_user_input_manager,survey.user_input manager,model_survey_user_input,base.group_survey_manager,1,1,1,1
|
||||
access_survey_user_input_line_manager,survey.user_input_line manager,model_survey_user_input_line,base.group_survey_manager,1,1,1,1
|
||||
|
||||
|
@@ -1,15 +1,12 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
console.debug("[survey] Custom JS for survey is loading...");
|
||||
|
||||
var the_form = $('.js_surveyform');
|
||||
var prefill_controller = the_form.attr("data-prefill");
|
||||
var validate_controller = the_form.attr("data-validate");
|
||||
var submit_controller = the_form.attr("data-submit");
|
||||
|
||||
console.log(prefill_controller);
|
||||
console.log(validate_controller);
|
||||
console.log(submit_controller);
|
||||
|
||||
|
||||
// function prefill(form){
|
||||
// return false;
|
||||
// }
|
||||
@@ -22,29 +19,30 @@ $(document).ready(function () {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// startsWith compatibility patch with old browsers
|
||||
// if (typeof String.prototype.startsWith != 'function') {
|
||||
// String.prototype.startsWith = function (str){
|
||||
// return this.slice(0, str.length) == str;
|
||||
// };
|
||||
// }
|
||||
|
||||
// function that display error messages
|
||||
var display_form_error = function (question_id, err_msg){
|
||||
$('#survey_' + question_id + '>.js_errzone').append('<p>' + err_msg + '</p>').show();
|
||||
};
|
||||
|
||||
//$('.js_errzone').hide();
|
||||
|
||||
console.debug("[survey] Custom JS for survey loaded");
|
||||
|
||||
// for each question
|
||||
// check if answer exists and is not empty and is mandatory
|
||||
// check if answer type is ok
|
||||
// check if number of answers is ok
|
||||
// check if answer has good qualities
|
||||
// Parameters for form submission
|
||||
$('.js_surveyform').ajaxForm({
|
||||
url: submit_controller,
|
||||
type: 'POST',
|
||||
dataType: 'json', // answer expected type
|
||||
beforeSubmit: function(){
|
||||
$('.js_errzone').html("").hide();
|
||||
},
|
||||
success: function(response, status, xhr, wfe){
|
||||
if(_.has(response, 'errors')){
|
||||
_.each(_.keys(response.errors), function(key){
|
||||
$("#" + key + '>.js_errzone').append('<p>' + response.errors[key] + '</p>').show();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else if (_.has(response, 'redirect')){
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
console.error("Something bad happened during AJAX request :(");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handles the event when a question is focused out
|
||||
$('.question-wrapper').focusout(
|
||||
@@ -52,114 +50,6 @@ $(document).ready(function () {
|
||||
console.debug("[survey] Focus lost on question " + $(this).attr("id"));
|
||||
});
|
||||
|
||||
// Parameters for form submission
|
||||
$('.js_surveyform').ajaxForm({
|
||||
type: 'POST',
|
||||
contentType: 'application/json', // request mimetype
|
||||
dataType: 'json', // answer expected type
|
||||
//success:
|
||||
//error:
|
||||
url: submit_controller
|
||||
});
|
||||
|
||||
$('.js_surveyform').submit(function(){
|
||||
$(this).ajaxSubmit();
|
||||
return false;
|
||||
});
|
||||
|
||||
// $('form').submit(
|
||||
// function(){
|
||||
// console.debug("[survey] Submit button clicked");
|
||||
// var valid = true;
|
||||
|
||||
// $('.js_errzone').html("").hide();
|
||||
|
||||
// $('.question-wrapper').each(
|
||||
// function(){
|
||||
// var qparams = jQuery.parseJSON($(this).attr('data-oe-survey-qparams'));
|
||||
// var question_id = $(this).attr('id').slice(7); //all the ids of question containers start with "survey_"
|
||||
// var candidate_answers = _.filter(answers, function(item){return item.name.startsWith(question_id);});
|
||||
// var answer_value;
|
||||
|
||||
// if(qparams.type === 'free_text'){
|
||||
// answer_value = candidate_answers[0].value.trim();
|
||||
// if(qparams.constr_mandatory === true && answer_value === ''){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "This question is mandatory");
|
||||
// }
|
||||
|
||||
|
||||
// } else if (qparams.type === 'textbox'){
|
||||
// answer_value = candidate_answers[0].value.trim();
|
||||
// if(qparams.constr_mandatory === true && answer_value === ''){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "This question is mandatory");
|
||||
// }
|
||||
|
||||
|
||||
// } else if (qparams.type === 'numerical_box'){
|
||||
// answer_value = candidate_answers[0].value.trim();
|
||||
// if(qparams.constr_mandatory === true && answer_value === ''){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "This question is mandatory");
|
||||
|
||||
// }
|
||||
// if(answer_value !== ''){
|
||||
// if(_.isNaN(parseFloat(answer_value))){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "You must answer with a number");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// } else if (qparams.type === 'datetime'){
|
||||
// answer_value = candidate_answers[0].value.trim();
|
||||
// if(qparams.constr_mandatory === true && answer_value === ''){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "This question is mandatory");
|
||||
// }
|
||||
// if(answer_value !== ''){
|
||||
// console.warning("Date format validation is not yet implemented");
|
||||
// }
|
||||
|
||||
// } else if (qparams.type === 'simple_choice_scale' || qparams.type === 'simple_choice_dropdown'){
|
||||
// if(qparams.constr_mandatory === true && _.size(candidate_answers) === 0){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "This question is mandatory");
|
||||
// }
|
||||
// // if answers:
|
||||
// // answer_value = candidate_answers[0].value;
|
||||
|
||||
|
||||
|
||||
// } else if (qparams.type === 'multiple_choice'){
|
||||
// var answ_nbr = _.size(candidate_answers);
|
||||
// if(qparams.constr_mandatory === true && answ_nbr <= 0){
|
||||
// valid = false;
|
||||
// display_form_error(question_id, "This question is mandatory");
|
||||
// }
|
||||
|
||||
|
||||
// // } else if (qparams.type === 'vector'){
|
||||
|
||||
|
||||
|
||||
// // } else if (qparams.type === 'matrix'){
|
||||
|
||||
|
||||
|
||||
// } else {
|
||||
// console.warning('Not supported question type!');
|
||||
// }
|
||||
|
||||
|
||||
// });
|
||||
|
||||
// return valid;
|
||||
|
||||
// // _.filter(answers, function(item){return item.name.startsWith(question_id);})
|
||||
// return;
|
||||
// });
|
||||
|
||||
console.debug("[survey] Custom JS for survey loaded!");
|
||||
|
||||
});
|
||||
+32
-21
@@ -345,24 +345,31 @@ class survey_question(osv.osv):
|
||||
oldname='descriptive_text'),
|
||||
|
||||
# Answer
|
||||
'type': fields.selection([('free_text', 'Free Text'),
|
||||
'type': fields.selection([('free_text', 'Long text zone'),
|
||||
('textbox', 'Text box'),
|
||||
('numerical_box', 'Numerical box'),
|
||||
('datetime', 'Date and Time'),
|
||||
('simple_choice_scale', 'One choice on a scale'),
|
||||
('simple_choice_dropdown', 'One choice in a menu'),
|
||||
('multiple_choice', 'Some choices in checkboxes'),
|
||||
('simple_choice', 'Multiple choice (one answer)'),
|
||||
('multiple_choice', 'Multiple choice (multiple answers)'),
|
||||
('matrix', 'Matrix')], 'Question Type', required=1),
|
||||
'suggested_answers_ids': fields.one2many('survey.suggestion',
|
||||
'labels_ids': fields.one2many('survey.label',
|
||||
'question_id', 'Suggested answers', oldname='answer_choice_ids'),
|
||||
|
||||
# Display options
|
||||
'simple_choice_display' : fields.selection([('1col', 'Buttons (1 column)'),
|
||||
('2col', 'Buttons (2 columns)'),
|
||||
('3col', 'Buttons (3 columns)'),
|
||||
('horizontal', 'Buttons (horizontal)'),
|
||||
('dropdown', 'Dropdown menu'),],
|
||||
'Display mode', ),
|
||||
|
||||
# Comments
|
||||
'comments_allowed': fields.boolean('Allow comments',
|
||||
oldname="allow_comment"),
|
||||
'comment_children_ids': fields.one2many('survey.question_id',
|
||||
'parent_id', 'Comment question'), # one2one in fact
|
||||
'comment_count_as_answer': fields.boolean('Make Comment Field an \
|
||||
Answer Choice', oldname='make_comment_field'),
|
||||
#'comment_children_ids': fields.one2many('survey.question_id',
|
||||
# 'parent_id', 'Comment question'), # one2one in fact
|
||||
'comment_count_as_answer': fields.boolean('Comment field is an answer choice',
|
||||
oldname='make_comment_field'),
|
||||
|
||||
# Validation
|
||||
'validation_required': fields.boolean('Validate entry',
|
||||
@@ -371,7 +378,7 @@ class survey_question(osv.osv):
|
||||
('has_length', 'Must have a specific length'),
|
||||
('is_integer', 'Must be an integer'),
|
||||
('is_decimal', 'Must be a decimal number'),
|
||||
('is_date', 'Must be a date'),
|
||||
#('is_date', 'Must be a date'),
|
||||
('is_email', 'Must be an email address')
|
||||
], 'Validation type'),
|
||||
'validation_length_min': fields.integer('Minimum length'),
|
||||
@@ -382,8 +389,7 @@ class survey_question(osv.osv):
|
||||
'validation_max_int_value': fields.integer('Maximum value'),
|
||||
'validation_min_date': fields.date('Start date range'),
|
||||
'validation_max_date': fields.date('End date range'),
|
||||
'validation_error_msg': fields.char("Error message if validation \
|
||||
fails", oldname='validation_valid_err_msg'),
|
||||
'validation_error_msg': fields.char("Error message", oldname='validation_valid_err_msg'),
|
||||
|
||||
# Constraints on number of answers
|
||||
'constr_mandatory': fields.boolean('Mandatory question',
|
||||
@@ -398,23 +404,28 @@ class survey_question(osv.osv):
|
||||
oldname='maximum_req_ans'),
|
||||
'constr_minimum_req_ans': fields.integer('Minimum Required Answer',
|
||||
oldname='minimum_req_ans'),
|
||||
'constr_error_msg': fields.char("Error message if constraints fails",
|
||||
'constr_error_msg': fields.char("Error message",
|
||||
oldname='req_error_msg'),
|
||||
}
|
||||
_defaults = {
|
||||
'page_id': lambda s, cr, uid, c: c.get('page_id'),
|
||||
'sequence': 1,
|
||||
'type': 'free_text',
|
||||
'validation_error_msg': lambda s, cr, uid, c: _('The answer you entered has an invalid format.'),
|
||||
'simple_choice_display': '1col',
|
||||
'constr_type': 'at least',
|
||||
'constr_minimum_req_ans': 1,
|
||||
'constr_error_msg': lambda s, cr, uid, c:
|
||||
_('This question requires an answer.'),
|
||||
'validation_error_msg': lambda s, cr, uid, c: _('The answer you entered has an invalid format.'),
|
||||
'validation_required': 'False',
|
||||
'validation_length_min': 0,
|
||||
'validation_length_max': 5000,
|
||||
}
|
||||
_sql_constraints = [('validation_length', 'CHECK (validation_length_min <= validation_length_max)', 'Max length cannot be smaller than min length!')]
|
||||
_sql_constraints = [
|
||||
('positive_len_min', 'CHECK (validation_length_min >= 0)', 'A length must be positive!'),
|
||||
('positive_len_max', 'CHECK (validation_length_max >= 0)', 'A length must be positive!'),
|
||||
('validation_length', 'CHECK (validation_length_min <= validation_length_max)', 'Max length cannot be smaller than min length!'),
|
||||
('validation_float', 'CHECK (validation_min_float_value <= validation_max_float_value)', 'Max value cannot be smaller than min value!'),
|
||||
('validation_int', 'CHECK (validation_min_int_value <= validation_max_int_value)', 'Max value cannot be smaller than min value!'),
|
||||
('validation_date', 'CHECK (validation_min_date <= validation_max_date)', 'Max date cannot be smaller than min date!')
|
||||
]
|
||||
|
||||
def on_change_page_id(self, cr, uid, ids, page_id, context=None):
|
||||
if page_id:
|
||||
@@ -527,9 +538,9 @@ class survey_question(osv.osv):
|
||||
}
|
||||
|
||||
|
||||
class survey_suggestion(osv.osv):
|
||||
class survey_label(osv.osv):
|
||||
''' A suggested answer for a question '''
|
||||
_name = 'survey.suggestion'
|
||||
_name = 'survey.label'
|
||||
_rec_name = 'value'
|
||||
|
||||
_columns = {
|
||||
@@ -678,7 +689,7 @@ class survey_user_input_line(osv.osv):
|
||||
'value_number': fields.float("Numerical answer"),
|
||||
'value_date': fields.datetime("Date answer"),
|
||||
'value_free_text': fields.text("Free Text answer"),
|
||||
'value_suggested': fields.many2one('survey.suggestion'),
|
||||
'value_suggested': fields.many2one('survey.label'),
|
||||
}
|
||||
_defaults = {
|
||||
'skipped': False,
|
||||
|
||||
@@ -402,14 +402,14 @@
|
||||
<field name="description" />
|
||||
</group>
|
||||
|
||||
<!-- Suggestions -->
|
||||
<group colspan="2" string="Choice of answers" attrs="{'invisible':[('type','not in',['simple_choice_dropdown',
|
||||
'multiple_choice','simple_choice_scale'])]}">
|
||||
<field name="suggested_answers_ids" nolabel="1" context="{'default_question_id': active_id}">
|
||||
<!-- Labels -->
|
||||
<group colspan="2" string="Choice of answers" attrs="{'invisible':[('type','not in',['simple_choice', 'multiple_choice'])]}">
|
||||
<field colspan="2" name="labels_ids" nolabel="1" context="{'default_question_id': active_id}">
|
||||
<tree string="Suggested answers" editable="bottom">
|
||||
<field name="value" />
|
||||
</tree>
|
||||
</field>
|
||||
<field colspan="2" name="simple_choice_display" string="Display mode"/>
|
||||
</group>
|
||||
|
||||
<!-- Sub-questions -->
|
||||
@@ -422,7 +422,7 @@
|
||||
<group colspan="2" string="Question required">
|
||||
<field name="constr_mandatory" string="Is this question mandatory?"/>
|
||||
<group attrs="{'invisible':[('constr_mandatory','=',False)]}">
|
||||
<group attrs="{'invisible':[('type','not in',['numerical_box'])]}">
|
||||
<group attrs="{'invisible':[('type','not in',['matrix'])]}">
|
||||
<field name="constr_type" string="Users must answer..."/>
|
||||
<group attrs="{'invisible':[('constr_type','not in',['at least','a range'])]}">
|
||||
<field name="constr_minimum_req_ans" nolabel="1" />
|
||||
@@ -436,7 +436,7 @@
|
||||
</group>
|
||||
|
||||
<!-- Answer validation -->
|
||||
<group colspan="1" string="Answer Validation" attrs="{'invisible':[('type','not in',['textbox','numerical_box','datetime'])]}">
|
||||
<group colspan="1" string="Answer Validation" attrs="{'invisible':[('type','not in',['textbox'])]}">
|
||||
<group>
|
||||
<field name="validation_required"/>
|
||||
|
||||
@@ -464,7 +464,7 @@
|
||||
</group>
|
||||
|
||||
<!-- Comments -->
|
||||
<group colspan="2" string="Comments" attrs="{'invisible':[('type','in',['free_text','textbox'])]}">
|
||||
<group colspan="2" string="Comments" attrs="{'invisible':[('type','not in',['simple_choice','multiple_choice'])]}">
|
||||
<field name='comments_allowed' />
|
||||
<group attrs="{'invisible':[('comments_allowed','=',False)]}">
|
||||
<field name='comment_count_as_answer' />
|
||||
@@ -518,9 +518,9 @@
|
||||
<field name="context">{'search_default_group_by_survey': True, 'search_default_group_by_page': True}</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_survey_suggestion_form">
|
||||
<field name="name">Survey Suggestions</field>
|
||||
<field name="res_model">survey.suggestion</field>
|
||||
<record model="ir.actions.act_window" id="action_survey_label_form">
|
||||
<field name="name">Survey labels</field>
|
||||
<field name="res_model">survey.label</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<!--<field name="view_id" ref="survey_question_tree"></field>
|
||||
@@ -666,7 +666,7 @@
|
||||
<act_window domain="[('question_id', '=', active_id)]"
|
||||
id="act_survey_answer"
|
||||
name="Answers"
|
||||
res_model="survey.suggestion"
|
||||
res_model="survey.label"
|
||||
src_model="survey.question"/>
|
||||
|
||||
<!--
|
||||
@@ -702,8 +702,8 @@
|
||||
action="action_survey_question_form" parent="menu_surveys_configuration"
|
||||
sequence="2"/>
|
||||
|
||||
<menuitem name="Survey Suggestions" id="menu_survey_suggestion_form1"
|
||||
action="action_survey_suggestion_form" parent="menu_surveys_configuration"
|
||||
<menuitem name="Survey labels" id="menu_survey_label_form1"
|
||||
action="action_survey_label_form" parent="menu_surveys_configuration"
|
||||
sequence="3"/>
|
||||
|
||||
<!-- web -->
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
<h1>Open surveys</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<tr><th>Title</th><th>Description</th><th>Category</th></tr>
|
||||
<tr><th>Title</th><th>Category</th></tr>
|
||||
<t t-foreach="surveys" t-as="survey">
|
||||
<tr>
|
||||
<td><a t-att-href="survey.public_url"><span t-field="survey.title" /></a></td>
|
||||
<td><t t-if="survey.description"><span t-field="survey.description"/></t></td>
|
||||
<td><span t-field="survey.category"/></td>
|
||||
</tr>
|
||||
</t>
|
||||
@@ -112,6 +111,10 @@
|
||||
|
||||
<t t-if="question.type in ['free_text']"><t t-call="survey.free_text"/></t>
|
||||
<t t-if="question.type in ['textbox']"><t t-call="survey.textbox"/></t>
|
||||
<t t-if="question.type in ['numerical_box']"><t t-call="survey.numerical_box"/></t>
|
||||
<t t-if="question.type in ['datetime']"><t t-call="survey.datetime"/></t>
|
||||
<t t-if="question.type in ['simple_choice']"><t t-call="survey.simple_choice"/></t>
|
||||
|
||||
|
||||
<!-- <h2><span t-field='question.question' /> <t t-if="question.constr_mandatory"><abbr title="This question is mandatory." class="text-danger">*</abbr></t></h2>
|
||||
<p class="text-muted"><t t-if="question.description"><span t-field='question.description'/></t></p> -->
|
||||
@@ -120,41 +123,19 @@
|
||||
<!-- instanciating different types of questions -->
|
||||
<!--
|
||||
|
||||
<t t-if="question.type in ['numerical_box']">
|
||||
<input type="number" step=".1" class="form-control" t-att-name="prefix"/>
|
||||
</t>
|
||||
|
||||
<t t-if="question.type in ['datetime']" >
|
||||
<input type="datetime-local" class="form-control" t-att-name="prefix" placeholder="jj-mm-aaaaThh:mm" />
|
||||
|
||||
TODO: replace html5 datetime widget by OpenERP-CMS datetime widget
|
||||
</t>
|
||||
|
||||
<t t-if="question.type in ['simple_choice_scale']">
|
||||
<t t-foreach='question.suggested_answers_ids' t-as='suggestion'>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" t-att-value='suggestion.value' />
|
||||
<span t-field='suggestion.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-if="question.type in ['simple_choice_dropdown']">
|
||||
<select class="form-control" t-att-name="prefix">
|
||||
<option disabled="1" selected="1">Choose...</option>
|
||||
<t t-foreach='question.suggested_answers_ids' t-as='suggestion'>
|
||||
<option><t t-esc='suggestion.value'/></option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
|
||||
<t t-if="question.type in ['multiple_choice']">
|
||||
<t t-foreach='question.suggested_answers_ids' t-as='suggestion'>
|
||||
<t t-foreach='question.suggested_answers_ids' t-as='label'>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" t-att-name="prefix + '_' + suggestion.id.__str__()" t-att-value='suggestion.value' />
|
||||
<span t-field='suggestion.value'/>
|
||||
<input type="checkbox" t-att-name="prefix + '_' + label.id.__str__()" t-att-value='label.value' />
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
@@ -194,6 +175,100 @@
|
||||
<input type="text" class="form-control" t-att-name="prefix"/>
|
||||
</template>
|
||||
|
||||
<template id="numerical_box" name="Numerical box">
|
||||
<h2><span t-field='question.question' /><t t-if="question.constr_mandatory"><abbr title="This question is mandatory." class="text-danger">*</abbr></t></h2>
|
||||
<p class="text-muted"><t t-if="question.description"><span t-field='question.description'/></t></p>
|
||||
<input type="number" step=".1" class="form-control" t-att-name="prefix"/>
|
||||
</template>
|
||||
|
||||
<template id="datetime" name="Numerical box">
|
||||
<h2><span t-field='question.question' /><t t-if="question.constr_mandatory"><abbr title="This question is mandatory." class="text-danger">*</abbr></t></h2>
|
||||
<p class="text-muted"><t t-if="question.description"><span t-field='question.description'/></t></p>
|
||||
<input type="datetime-local" class="form-control" t-att-name="prefix" placeholder="jj-mm-aaaa hh:mm" />
|
||||
</template>
|
||||
|
||||
<template id="simple_choice" name="Simple choice">
|
||||
<h2><span t-field='question.question' /><t t-if="question.constr_mandatory"><abbr title="This question is mandatory." class="text-danger">*</abbr></t></h2>
|
||||
<p class="text-muted"><t t-if="question.description"><span t-field='question.description'/></t></p>
|
||||
|
||||
<t t-if="question.simple_choice_display == 'dropdown'">
|
||||
<select class="form-control" t-att-name="prefix">
|
||||
<option disabled="1" selected="1">Choose...</option>
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
<option><t t-esc='label.value'/></option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
<t t-if="question.simple_choice_display == 'horizontal'">
|
||||
<div>
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
<div class="radio-inline">
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.value' />
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="question.simple_choice_display == '1col'">
|
||||
<div>
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.value' />
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="question.simple_choice_display == '2col'">
|
||||
<div class="row">
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
<div class="radio col-md-6">
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.value' />
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="question.simple_choice_display == '3col'">
|
||||
<div class="row">
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
<div class="radio col-md-4">
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.value' />
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
<t t-if="question.type in ['simple_choice_scale']">
|
||||
<t t-foreach='question.suggested_answers_ids' t-as='label'>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.value' />
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-if="question.type in ['simple_choice_dropdown']">
|
||||
</t>
|
||||
-->
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Printable view of a survey (all pages) -->
|
||||
<template id="survey_print" name="Survey">
|
||||
<t t-call="website.layout">
|
||||
|
||||
Reference in New Issue
Block a user