2010-01-20 14:28:28 +00:00
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2013-10-31 15:45:33 +00:00
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
2010-01-20 14:28:28 +00:00
#
2013-10-21 09:03:19 +00:00
# This program is free software: you can redistribute it and / or modify
2010-12-08 13:59:16 +00:00
# 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.
2010-01-20 14:28:28 +00:00
#
# 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
2010-12-08 13:59:16 +00:00
# GNU Affero General Public License for more details.
2010-01-20 14:28:28 +00:00
#
2010-12-08 13:59:16 +00:00
# You should have received a copy of the GNU Affero General Public License
2013-10-31 15:45:33 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2010-01-20 14:28:28 +00:00
#
##############################################################################
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
from openerp . tools . translate import _
2013-11-29 16:10:32 +00:00
from openerp . tools import DEFAULT_SERVER_DATETIME_FORMAT as DF
2013-11-29 08:49:14 +00:00
from urlparse import urljoin
2013-11-29 16:10:32 +00:00
import datetime
2013-11-29 08:49:14 +00:00
import logging
2013-11-29 16:10:32 +00:00
import re
import uuid
2013-11-29 08:49:14 +00:00
_logger = logging . getLogger ( __name__ )
2013-10-21 09:03:19 +00:00
2010-01-27 13:55:23 +00:00
2013-10-28 07:51:21 +00:00
class survey_survey ( osv . osv ) :
2013-10-22 15:10:51 +00:00
''' Settings for a multi-page/multi-question survey.
2013-11-19 08:28:50 +00:00
Each survey can have one or more attached pages , and each page can display
2013-10-22 15:10:51 +00:00
one or more questions .
'''
_name = ' survey.survey '
2010-01-20 14:28:28 +00:00
_description = ' Survey '
_rec_name = ' title '
2013-10-21 09:03:19 +00:00
_inherit = [ ' mail.thread ' , ' ir.needaction_mixin ' ]
2010-01-27 13:55:23 +00:00
2013-10-22 15:10:51 +00:00
# Protected methods #
2013-11-19 08:28:50 +00:00
def _has_questions ( self , cr , uid , ids , context = None ) :
2013-10-25 09:56:54 +00:00
""" Ensure that this survey has at least one page with at least one
2013-11-20 15:13:01 +00:00
question . """
2013-10-28 16:15:31 +00:00
for survey in self . browse ( cr , uid , ids , context = context ) :
2013-10-25 09:56:54 +00:00
if not survey . page_ids or not [ page . question_ids
for page in survey . page_ids if page . question_ids ] :
2013-11-19 08:28:50 +00:00
return False
return True
2013-10-24 09:26:33 +00:00
## Function fields ##
2013-10-21 09:03:19 +00:00
def _get_tot_start_survey ( self , cr , uid , ids , name , arg , context = None ) :
2013-10-23 09:03:42 +00:00
""" Returns the number of started instances of this survey, be they
completed or not """
2013-10-21 09:03:19 +00:00
res = dict ( ( id , 0 ) for id in ids )
2013-10-28 07:51:21 +00:00
sur_res_obj = self . pool . get ( ' survey.user_input ' )
2013-10-21 09:03:19 +00:00
for id in ids :
2013-10-28 16:15:31 +00:00
res [ id ] = sur_res_obj . search ( cr , uid , # SUPERUSER_ID,
2013-10-28 07:51:21 +00:00
[ ( ' survey_id ' , ' = ' , id ) , ( ' state ' , ' = ' , ' skip ' ) ] ,
context = context , count = True )
2013-10-21 09:03:19 +00:00
return res
def _get_tot_comp_survey ( self , cr , uid , ids , name , arg , context = None ) :
2013-10-23 09:03:42 +00:00
""" Returns the number of completed instances of this survey """
2013-10-21 09:03:19 +00:00
res = dict ( ( id , 0 ) for id in ids )
2013-10-28 07:51:21 +00:00
sur_res_obj = self . pool . get ( ' survey.user_input ' )
2013-10-21 09:03:19 +00:00
for id in ids :
2013-10-28 16:15:31 +00:00
res [ id ] = sur_res_obj . search ( cr , uid , # SUPERUSER_ID,
2013-10-28 07:51:21 +00:00
[ ( ' survey_id ' , ' = ' , id ) , ( ' state ' , ' = ' , ' done ' ) ] ,
context = context , count = True )
2013-10-21 09:03:19 +00:00
return res
def _get_public_url ( self , cr , uid , ids , name , arg , context = None ) :
2013-10-23 09:03:42 +00:00
""" Computes a public URL for the survey """
2013-10-21 09:03:19 +00:00
res = dict ( ( id , 0 ) for id in ids )
2013-10-23 09:03:42 +00:00
base_url = self . pool . get ( ' ir.config_parameter ' ) . get_param ( cr , uid ,
' web.base.url ' )
for survey_browse in self . browse ( cr , uid , ids , context = context ) :
2013-11-25 08:04:46 +00:00
res [ survey_browse . id ] = urljoin ( base_url , " survey/start/ %s / "
2013-11-05 10:36:54 +00:00
% survey_browse . id )
2013-10-21 09:03:19 +00:00
return res
2010-01-27 13:55:23 +00:00
2013-10-22 15:10:51 +00:00
# Model fields #
2010-01-20 14:28:28 +00:00
_columns = {
2013-10-23 09:03:42 +00:00
' title ' : fields . char ( ' Title ' , size = 128 , required = 1 ,
translate = True ) ,
2013-11-26 12:53:03 +00:00
' res_model ' : fields . char ( ' Category ' ) ,
2013-10-22 15:10:51 +00:00
' page_ids ' : fields . one2many ( ' survey.page ' , ' survey_id ' , ' Pages ' ) ,
2013-10-28 14:43:14 +00:00
' date_open ' : fields . datetime ( ' Opening date ' ) ,
' date_close ' : fields . datetime ( ' Closing date ' ) ,
2013-10-23 09:03:42 +00:00
' user_input_limit ' : fields . integer ( ' Automatic closing limit ' ,
2013-11-20 15:13:01 +00:00
help = " Limits the number of instances of this survey that can be completed (if set to 0, no limit is applied) " ,
2013-10-23 09:03:42 +00:00
oldname = ' max_response_limit ' ) ,
2013-10-22 15:10:51 +00:00
' state ' : fields . selection (
[ ( ' draft ' , ' Draft ' ) , ( ' open ' , ' Open ' ) , ( ' close ' , ' Closed ' ) ,
( ' cancel ' , ' Cancelled ' ) ] , ' Status ' , required = 1 , readonly = 1 ,
translate = 1 ) ,
2013-11-25 08:04:46 +00:00
' visible_to_user ' : fields . boolean ( ' Public in website ' ,
2013-11-20 15:13:01 +00:00
help = " If unchecked, only invited users will be able to open the survey. " ) ,
2013-10-23 09:03:42 +00:00
' auth_required ' : fields . boolean ( ' Login required ' ,
2013-11-20 15:13:01 +00:00
help = " Users with a public link will be requested to login before taking part to the survey " ,
oldname = " authenticate " ) ,
' users_can_go_back ' : fields . boolean ( ' Users can go back ' ,
help = " If checked, users can go back to previous pages. " ) ,
2013-10-23 09:03:42 +00:00
' tot_start_survey ' : fields . function ( _get_tot_start_survey ,
string = " Number of started surveys " , type = " integer " ) ,
' tot_comp_survey ' : fields . function ( _get_tot_comp_survey ,
string = " Number of completed surveys " , type = " integer " ) ,
2013-11-15 07:51:50 +00:00
' description ' : fields . html ( ' Description ' , translate = True ,
oldname = " description " , help = " A long description of the purpose of the survey " ) ,
2012-07-05 12:43:05 +00:00
' color ' : fields . integer ( ' Color Index ' ) ,
2013-10-23 09:03:42 +00:00
' user_input_ids ' : fields . one2many ( ' survey.user_input ' , ' survey_id ' ,
2013-11-20 16:15:19 +00:00
' User responses ' , readonly = 1 ) ,
2013-10-23 09:03:42 +00:00
' public_url ' : fields . function ( _get_public_url ,
2013-11-25 08:04:46 +00:00
string = " Public link " , type = " char " ) ,
2013-10-23 09:03:42 +00:00
' email_template_id ' : fields . many2one ( ' email.template ' ,
' Email Template ' , ondelete = ' set null ' ) ,
2013-11-15 07:51:50 +00:00
' thank_you_message ' : fields . html ( ' Thank you message ' , translate = True ,
help = " This message will be displayed when survey is completed " )
2010-01-20 14:28:28 +00:00
}
2013-11-19 08:28:50 +00:00
2010-01-20 14:28:28 +00:00
_defaults = {
2013-10-23 09:03:42 +00:00
' user_input_limit ' : 0 ,
2013-10-22 15:10:51 +00:00
' state ' : ' draft ' ,
2013-10-21 09:03:19 +00:00
' visible_to_user ' : True ,
2013-10-23 09:03:42 +00:00
' auth_required ' : True ,
2013-11-20 15:13:01 +00:00
' users_can_go_back ' : False
2010-01-20 14:28:28 +00:00
}
2013-11-20 15:13:01 +00:00
_sql_constraints = {
( ' positive_user_input_limit ' , ' CHECK (user_input_limit >= 0) ' , ' Automatic closing limit must be positive ' )
}
2011-08-03 06:32:01 +00:00
2013-11-20 15:13:01 +00:00
# Public methods #
2013-10-25 09:56:54 +00:00
2010-12-14 07:37:12 +00:00
def copy ( self , cr , uid , ids , default = None , context = None ) :
2010-12-09 05:36:29 +00:00
vals = { }
current_rec = self . read ( cr , uid , ids , context = context )
2012-09-24 16:26:45 +00:00
title = _ ( " %s (copy) " ) % ( current_rec . get ( ' title ' ) )
2013-10-25 09:56:54 +00:00
vals [ ' title ' ] = title
2013-11-20 16:15:19 +00:00
vals [ ' user_input_ids ' ] = [ ]
2013-10-28 16:15:31 +00:00
return super ( survey_survey , self ) . copy ( cr , uid , ids , vals ,
context = context )
2010-02-05 11:25:40 +00:00
2013-12-12 11:04:35 +00:00
def next_page ( self , cr , uid , user_input , page_id , go_back = False , context = None ) :
''' The next page to display to the user, knowing that page_id is the id
of the last displayed page .
If page_id == 0 , it will always return the first page of the survey .
If all the pages have been displayed and go_back == False , it will
return None
If go_back == True , it will return the * previous * page instead of the
next page .
. . note : :
It is assumed here that a careful user will not try to set go_back
to True if she knows that the page to display is the first one !
( doing this will probably cause a giant worm to eat her house ) '''
survey = user_input . survey_id
pages = list ( enumerate ( survey . page_ids ) )
# First page
if page_id == 0 :
return ( pages [ 0 ] [ 1 ] , 0 , len ( pages ) == 1 )
current_page_index = pages . index ( ( filter ( lambda p : p [ 1 ] . id == page_id , pages ) ) [ 0 ] )
# All the pages have been displayed
if current_page_index == len ( pages ) - 1 and not go_back :
return ( None , - 1 , False )
# Let's get back, baby!
elif go_back and survey . users_can_go_back :
return ( pages [ current_page_index - 1 ] [ 1 ] , current_page_index - 1 , False )
else :
# This will show the last page
if current_page_index == len ( pages ) - 2 :
return ( pages [ current_page_index + 1 ] [ 1 ] , current_page_index + 1 , True )
# This will show a regular page
else :
return ( pages [ current_page_index + 1 ] [ 1 ] , current_page_index + 1 , False )
2013-10-25 09:56:54 +00:00
def action_print_survey_questions ( self , cr , uid , ids , context = None ) :
''' Generates a printable view of an empty survey '''
pass
2013-10-21 09:03:19 +00:00
2013-10-25 09:56:54 +00:00
def action_print_survey_user_inputs ( self , cr , uid , ids , context = None ) :
''' Generates printable views of answered surveys '''
pass
2012-09-24 16:26:45 +00:00
2013-10-25 09:56:54 +00:00
def action_print_survey_statistics ( self , cr , uid , ids , context = None ) :
''' Generates a printable view of the survey results '''
pass
2013-10-21 09:03:19 +00:00
def action_fill_survey ( self , cr , uid , ids , context = None ) :
2013-10-25 09:56:54 +00:00
''' Open a survey in filling view '''
2013-10-21 09:03:19 +00:00
id = ids [ 0 ]
survey = self . browse ( cr , uid , id , context = context )
2013-10-28 14:43:14 +00:00
context . update ( { ' edit ' : False , ' survey_id ' : id ,
' survey_token ' : survey . token ,
' ir_actions_act_window_target ' : ' inline ' } )
2012-04-17 05:50:10 +00:00
return {
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' survey.question.wiz ' ,
' type ' : ' ir.actions.act_window ' ,
2013-10-21 09:03:19 +00:00
' target ' : ' inline ' ,
' name ' : survey . title ,
2012-08-21 05:36:25 +00:00
' context ' : context
}
2013-10-21 09:03:19 +00:00
def action_test_survey ( self , cr , uid , ids , context = None ) :
2013-10-25 09:56:54 +00:00
''' Open a survey in filling view '''
2013-10-21 09:03:19 +00:00
context . update ( { ' survey_test ' : True } )
return self . action_fill_survey ( cr , uid , ids , context = context )
def action_edit_survey ( self , cr , uid , ids , context = None ) :
2013-10-25 09:56:54 +00:00
''' Open a survey in edition view '''
2013-10-21 09:03:19 +00:00
id = ids [ 0 ]
context . update ( {
' survey_id ' : id ,
' edit ' : True ,
' ir_actions_act_window_target ' : ' new ' ,
} )
2012-10-11 09:36:46 +00:00
return {
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' survey.question.wiz ' ,
' type ' : ' ir.actions.act_window ' ,
' target ' : ' new ' ,
2013-10-21 09:03:19 +00:00
' name ' : self . browse ( cr , uid , id , context = context ) . title ,
2012-10-11 09:36:46 +00:00
' context ' : context
}
2013-10-25 09:56:54 +00:00
def action_send_survey ( self , cr , uid , ids , context = None ) :
''' Open a window to compose an email, pre-filled with the survey
message '''
2013-11-19 09:16:25 +00:00
if not self . _has_questions ( cr , uid , ids , context = None ) :
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' You can not send a survey that has no questions. ' ) )
2013-10-21 09:03:19 +00:00
2013-10-25 09:56:54 +00:00
survey_browse = self . pool . get ( ' survey.survey ' ) . browse ( cr , uid , ids ,
context = context ) [ 0 ]
2013-10-21 09:03:19 +00:00
if survey_browse . state != " open " :
2013-10-25 09:56:54 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) ,
_ ( " You cannot send invitations since the survey is not open. " ) )
2013-10-21 09:03:19 +00:00
2013-10-25 09:56:54 +00:00
assert len ( ids ) == 1 , ' This option should only be used for a single \
survey at a time . '
2013-10-21 09:03:19 +00:00
ir_model_data = self . pool . get ( ' ir.model.data ' )
2013-12-30 15:35:32 +00:00
templates = ir_model_data . get_object_reference ( cr , uid ,
' survey ' , ' email_template_survey ' )
template_id = templates [ 1 ] if len ( templates ) > 0 else False
2013-10-21 09:03:19 +00:00
ctx = dict ( context )
ctx . update ( {
2013-10-25 09:56:54 +00:00
' default_model ' : ' survey.survey ' ,
2013-10-21 09:03:19 +00:00
' default_res_id ' : ids [ 0 ] ,
' default_survey_id ' : ids [ 0 ] ,
' default_use_template ' : bool ( template_id ) ,
' default_template_id ' : template_id ,
' default_composition_mode ' : ' comment ' ,
' survey_state ' : survey_browse . state
} )
2012-08-21 05:36:25 +00:00
return {
2013-10-21 09:03:19 +00:00
' type ' : ' ir.actions.act_window ' ,
2012-08-21 05:36:25 +00:00
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
2013-10-21 09:03:19 +00:00
' res_model ' : ' survey.mail.compose.message ' ,
2012-08-21 05:36:25 +00:00
' target ' : ' new ' ,
2013-10-21 09:03:19 +00:00
' context ' : ctx ,
2012-04-17 05:50:10 +00:00
}
2012-10-11 09:36:46 +00:00
2013-11-20 15:13:01 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
new_state = vals . get ( ' state ' )
if new_state == ' draft ' :
vals . update ( { ' date_open ' : None } )
vals . update ( { ' date_close ' : None } )
2013-12-30 14:14:59 +00:00
self . message_post ( cr , uid , ids , body = """ <p>Survey drafted</p> """ , context = context )
2013-11-20 15:13:01 +00:00
elif new_state == ' open ' :
if self . _has_questions ( cr , uid , ids , context = None ) :
vals . update ( { ' date_open ' : fields . datetime . now ( ) , ' date_close ' : None } )
2013-12-30 14:14:59 +00:00
self . message_post ( cr , uid , ids , body = """ <p>Survey opened</p> """ , context = context )
2013-11-20 15:13:01 +00:00
else :
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' You can not open a survey that has no questions. ' ) )
elif new_state == ' close ' :
vals . update ( { ' date_close ' : fields . datetime . now ( ) } )
2013-12-30 14:14:59 +00:00
self . message_post ( cr , uid , ids , body = """ <p>Survey closed</p> """ , context = context )
elif new_state == ' cancel ' :
self . message_post ( cr , uid , ids , body = """ <p>Survey cancelled</p> """ , context = context )
2013-11-20 15:13:01 +00:00
return super ( survey_survey , self ) . write ( cr , uid , ids , vals , context = None )
2010-01-20 14:28:28 +00:00
class survey_page ( osv . osv ) :
2013-10-23 12:22:55 +00:00
''' A page for a survey.
Pages are essentially containers , allowing to group questions by ordered
screens .
2013-10-28 07:51:21 +00:00
. . note : :
2013-10-28 14:43:14 +00:00
A page should be deleted if the survey it belongs to is deleted . '''
2013-10-23 12:22:55 +00:00
2010-01-20 14:28:28 +00:00
_name = ' survey.page '
2013-10-23 12:22:55 +00:00
_description = ' Survey Page '
2010-01-20 14:28:28 +00:00
_rec_name = ' title '
_order = ' sequence '
2013-10-23 12:22:55 +00:00
# Model Fields #
2010-01-20 14:28:28 +00:00
_columns = {
2013-10-23 12:22:55 +00:00
' title ' : fields . char ( ' Page Title ' , size = 128 , required = 1 ,
translate = True ) ,
' survey_id ' : fields . many2one ( ' survey.survey ' , ' Survey ' ,
ondelete = ' cascade ' ) ,
' question_ids ' : fields . one2many ( ' survey.question ' , ' page_id ' ,
' Questions ' ) ,
' sequence ' : fields . integer ( ' Page number ' ) ,
2013-11-15 07:51:50 +00:00
' description ' : fields . html ( ' Description ' ,
2013-10-23 12:22:55 +00:00
help = " An introductory text to your page " , translate = True ,
oldname = " note " ) ,
2010-01-20 14:28:28 +00:00
}
2013-12-12 11:04:35 +00:00
_defaults = {
' sequence ' : 10
}
2010-01-20 14:28:28 +00:00
2013-10-23 12:22:55 +00:00
# Public methods #
2010-12-14 07:37:12 +00:00
def copy ( self , cr , uid , ids , default = None , context = None ) :
2010-12-09 05:36:29 +00:00
vals = { }
current_rec = self . read ( cr , uid , ids , context = context )
2012-09-24 16:26:45 +00:00
title = _ ( " %s (copy) " ) % ( current_rec . get ( ' title ' ) )
2013-10-21 09:03:19 +00:00
vals . update ( { ' title ' : title } )
2013-10-23 12:22:55 +00:00
return super ( survey_page , self ) . copy ( cr , uid , ids , vals ,
context = context )
2010-02-05 11:25:40 +00:00
2010-01-20 14:28:28 +00:00
class survey_question ( osv . osv ) :
2013-10-23 12:22:55 +00:00
''' Questions that will be asked in a survey.
2013-10-28 14:43:14 +00:00
Each question can have one of more suggested answers ( eg . in case of
2013-11-06 15:24:06 +00:00
dropdown choices , multi - answer checkboxes , radio buttons . . . ) . '''
2010-01-20 14:28:28 +00:00
_name = ' survey.question '
2013-11-19 13:28:18 +00:00
_description = ' Survey Question '
2010-01-20 14:28:28 +00:00
_rec_name = ' question '
_order = ' sequence '
2013-10-23 12:22:55 +00:00
# Model fields #
2010-01-20 14:28:28 +00:00
_columns = {
2013-10-24 09:26:33 +00:00
# Question metadata
2013-10-23 12:22:55 +00:00
' page_id ' : fields . many2one ( ' survey.page ' , ' Survey page ' ,
ondelete = ' cascade ' ) ,
' survey_id ' : fields . related ( ' page_id ' , ' survey_id ' , type = ' many2one ' ,
relation = ' survey.survey ' , string = ' Survey ' , store = True ) ,
2013-11-18 16:12:59 +00:00
' parent_id ' : fields . many2one ( ' survey.question ' , ' Parent question ' ,
ondelete = ' cascade ' ) ,
2013-11-05 15:28:21 +00:00
' sequence ' : fields . integer ( string = ' Sequence ' ) ,
2013-10-23 12:22:55 +00:00
2013-10-24 09:26:33 +00:00
# Question
' question ' : fields . char ( ' Question ' , required = 1 , translate = True ) ,
2013-11-15 07:51:50 +00:00
' description ' : fields . char ( ' Description ' , help = " Use this field to add \
2013-10-29 15:17:31 +00:00
additional explanations about your question " , translate=True,
oldname = ' descriptive_text ' ) ,
2013-10-23 12:22:55 +00:00
2013-10-24 09:26:33 +00:00
# Answer
2013-11-15 15:14:35 +00:00
' type ' : fields . selection ( [ ( ' free_text ' , ' Long text zone ' ) ,
2013-10-31 08:08:56 +00:00
( ' textbox ' , ' Text box ' ) ,
2013-10-24 09:26:33 +00:00
( ' numerical_box ' , ' Numerical box ' ) ,
2013-10-23 12:22:55 +00:00
( ' datetime ' , ' Date and Time ' ) ,
2013-11-15 15:14:35 +00:00
( ' simple_choice ' , ' Multiple choice (one answer) ' ) ,
( ' multiple_choice ' , ' Multiple choice (multiple answers) ' ) ,
2013-11-15 07:51:50 +00:00
( ' matrix ' , ' Matrix ' ) ] , ' Question Type ' , required = 1 ) ,
2013-11-26 12:53:03 +00:00
' matrix_subtype ' : fields . selection ( [ ( ' simple ' , ' One choice per line ' ) ,
( ' multiple ' , ' Several choices per line ' ) ] , ' Matrix Type ' ) ,
2013-11-15 15:14:35 +00:00
' labels_ids ' : fields . one2many ( ' survey.label ' ,
2013-10-29 15:17:31 +00:00
' question_id ' , ' Suggested answers ' , oldname = ' answer_choice_ids ' ) ,
2013-11-26 12:53:03 +00:00
' labels_ids_2 ' : fields . one2many ( ' survey.label ' ,
' question_id_2 ' , ' Suggested answers ' ) ,
2013-12-06 16:10:56 +00:00
# labels are used for proposed choices
# if question.type == simple choice | multiple choice
# -> only labels_ids is used
# if question.type == matrix
# -> labels_ids are the columns of the matrix
# -> labels_ids_2 are the rows of the matrix
2013-11-15 15:14:35 +00:00
# Display options
2013-11-25 08:04:46 +00:00
' column_nb ' : fields . selection ( [ ( ' 12 ' , ' 1 column choices ' ) ,
2013-11-26 12:53:03 +00:00
( ' 6 ' , ' 2 columns choices ' ) ,
( ' 4 ' , ' 3 columns choices ' ) ,
( ' 3 ' , ' 4 columns choices ' ) ,
( ' 2 ' , ' 6 columns choices ' ) ] ,
' Number of columns ' ) ,
2013-11-25 08:04:46 +00:00
' display_mode ' : fields . selection ( [ ( ' columns ' , ' Columns ' ) ,
2013-11-26 12:53:03 +00:00
( ' dropdown ' , ' Dropdown menu ' ) ] ,
2013-11-29 08:49:14 +00:00
' Display mode ' ) ,
2013-11-15 15:14:35 +00:00
2013-10-24 14:56:39 +00:00
# Comments
' comments_allowed ' : fields . boolean ( ' Allow comments ' ,
oldname = " allow_comment " ) ,
2013-11-26 12:53:03 +00:00
' comment_children_ids ' : fields . many2many ( ' survey.question ' ,
' question_comment_children_ids ' , ' comment_id ' , ' parent_id ' ,
' Comment question ' ) , # one2one in fact
2013-11-15 15:14:35 +00:00
' comment_count_as_answer ' : fields . boolean ( ' Comment field is an answer choice ' ,
oldname = ' make_comment_field ' ) ,
2013-10-24 14:56:39 +00:00
# Validation
' validation_required ' : fields . boolean ( ' Validate entry ' ,
oldname = ' is_validation_require ' ) ,
' validation_type ' : fields . selection ( [
2013-10-31 14:05:45 +00:00
( ' has_length ' , ' Must have a specific length ' ) ,
( ' is_integer ' , ' Must be an integer ' ) ,
( ' is_decimal ' , ' Must be a decimal number ' ) ,
2013-11-15 15:14:35 +00:00
#('is_date', 'Must be a date'),
2013-11-20 15:13:01 +00:00
( ' is_email ' , ' Must be an email address ' ) ] ,
2013-11-29 08:49:14 +00:00
' Validation type ' , translate = True ) ,
2013-11-15 07:51:50 +00:00
' validation_length_min ' : fields . integer ( ' Minimum length ' ) ,
' validation_length_max ' : fields . integer ( ' Maximum length ' ) ,
2013-10-31 08:08:56 +00:00
' validation_min_float_value ' : fields . float ( ' Minimum value ' ) ,
' validation_max_float_value ' : fields . float ( ' Maximum value ' ) ,
' validation_min_int_value ' : fields . integer ( ' Minimum value ' ) ,
' validation_max_int_value ' : fields . integer ( ' Maximum value ' ) ,
2013-11-13 13:55:32 +00:00
' validation_min_date ' : fields . date ( ' Start date range ' ) ,
' validation_max_date ' : fields . date ( ' End date range ' ) ,
2013-11-29 08:49:14 +00:00
' validation_error_msg ' : fields . char ( ' Error message ' ,
oldname = ' validation_valid_err_msg ' ,
translate = True ) ,
2013-10-23 12:22:55 +00:00
2013-12-06 09:17:44 +00:00
# Constraints on number of answers (matrices)
2013-10-24 09:26:33 +00:00
' constr_mandatory ' : fields . boolean ( ' Mandatory question ' ,
oldname = " is_require_answer " ) ,
2013-10-31 14:05:45 +00:00
' constr_type ' : fields . selection ( [ ( ' all ' , ' all ' ) ,
( ' at least ' , ' at least ' ) ,
( ' at most ' , ' at most ' ) ,
( ' exactly ' , ' exactly ' ) ,
( ' a range ' , ' a range ' ) ] ,
2013-10-29 15:17:31 +00:00
' Constraint on answers number ' , oldname = ' required_type ' ) ,
' constr_maximum_req_ans ' : fields . integer ( ' Maximum Required Answer ' ,
oldname = ' maximum_req_ans ' ) ,
' constr_minimum_req_ans ' : fields . integer ( ' Minimum Required Answer ' ,
oldname = ' minimum_req_ans ' ) ,
2013-11-15 15:14:35 +00:00
' constr_error_msg ' : fields . char ( " Error message " ,
2013-10-29 15:17:31 +00:00
oldname = ' req_error_msg ' ) ,
2010-01-20 14:28:28 +00:00
}
_defaults = {
2013-10-21 09:03:19 +00:00
' page_id ' : lambda s , cr , uid , c : c . get ( ' page_id ' ) ,
2013-12-12 11:04:35 +00:00
' sequence ' : 10 ,
2013-10-31 14:05:45 +00:00
' type ' : ' free_text ' ,
2013-11-26 12:53:03 +00:00
' matrix_subtype ' : ' simple ' ,
2013-11-25 08:04:46 +00:00
' column_nb ' : ' 12 ' ,
' display_mode ' : ' dropdown ' ,
2013-10-29 15:17:31 +00:00
' constr_type ' : ' at least ' ,
' constr_minimum_req_ans ' : 1 ,
2013-12-06 09:17:44 +00:00
' constr_maximum_req_ans ' : 1 ,
2013-10-31 14:05:45 +00:00
' constr_error_msg ' : lambda s , cr , uid , c :
_ ( ' This question requires an answer. ' ) ,
2013-11-15 15:14:35 +00:00
' validation_error_msg ' : lambda s , cr , uid , c : _ ( ' The answer you entered has an invalid format. ' ) ,
2013-11-18 16:12:59 +00:00
' validation_required ' : False ,
2010-01-20 14:28:28 +00:00
}
2013-11-15 15:14:35 +00:00
_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! ' ) ,
2013-12-06 09:17:44 +00:00
( ' validation_date ' , ' CHECK (validation_min_date <= validation_max_date) ' , ' Max date cannot be smaller than min date! ' ) ,
( ' constr_number ' , ' CHECK (constr_minimum_req_ans <= constr_maximum_req_ans) ' , ' Max number of answers cannot be smaller than min number! ' )
2013-11-15 15:14:35 +00:00
]
2010-01-20 14:28:28 +00:00
2013-10-31 15:45:33 +00:00
# def write(self, cr, uid, ids, vals, context=None):
# questions = self.read(cr, uid, ids, ['answer_choice_ids', 'type',
# 'required_type', 'req_ans', 'minimum_req_ans', 'maximum_req_ans',
# 'column_heading_ids', 'page_id', 'question'])
# for question in questions:
# col_len = len(question['column_heading_ids'])
# for col in vals.get('column_heading_ids', []):
# if type(col[2]) == type({}):
# col_len += 1
# else:
# col_len -= 1
# que_type = vals.get('type', question['type'])
# if que_type in ['matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'rating_scale']:
# if not col_len:
# raise osv.except_osv(_('Warning!'), _('You must enter one or more column headings for question "%s" of page %s.') % (question['question'], question['page_id'][1]))
# ans_len = len(question['answer_choice_ids'])
# for ans in vals.get('answer_choice_ids', []):
# if type(ans[2]) == type({}):
# ans_len += 1
# else:
# ans_len -= 1
# if que_type not in ['descriptive_text', 'single_textbox', 'comment', 'table']:
# if not ans_len:
# raise osv.except_osv(_('Warning!'), _('You must enter one or more Answers for question "%s" of page %s.') % (question['question'], question['page_id'][1]))
# req_type = vals.get('required_type', question['required_type'])
# if que_type in ['multiple_choice_multiple_ans', 'matrix_of_choices_only_one_ans', \
# 'matrix_of_choices_only_multi_ans', 'rating_scale', 'multiple_textboxes', \
# 'numerical_textboxes', 'date', 'date_and_time']:
# if req_type in ['at least', 'at most', 'exactly']:
# if 'req_ans' in vals:
# if not vals['req_ans'] or vals['req_ans'] > ans_len:
# raise osv.except_osv(_('Warning!'), _("#Required Answer you entered \
# is greater than the number of answer. \
# Please use a number that is smaller than %d.") % (ans_len + 1))
# else:
# if not question['req_ans'] or question['req_ans'] > ans_len:
# raise osv.except_osv(_('Warning!'), _("#Required Answer you entered is \
# greater than the number of answer.\
# Please use a number that is smaller than %d.") % (ans_len + 1))
# if req_type == 'a range':
# minimum_ans = 0
# maximum_ans = 0
# minimum_ans = 'minimum_req_ans' in vals and vals['minimum_req_ans'] or question['minimum_req_ans']
# maximum_ans = 'maximum_req_ans' in vals and vals['maximum_req_ans'] or question['maximum_req_ans']
# if not minimum_ans or minimum_ans > ans_len or not maximum_ans or maximum_ans > ans_len:
# raise osv.except_osv(_('Warning!'), _("Minimum Required Answer you\
# entered is greater than the number of answer. \
# Please use a number that is smaller than %d.") % (ans_len + 1))
# if maximum_ans <= minimum_ans:
# raise osv.except_osv(_('Warning!'), _("Maximum Required Answer is greater \
# than Minimum Required Answer"))
# return super(survey_question, self).write(cr, uid, ids, vals, context=context)
# def create(self, cr, uid, vals, context=None):
# page = self.pool.get('survey.page').browse(cr, uid, 'page_id' in vals and vals['page_id'] or context['page_id'], context=context)
# if 'answer_choice_ids' in vals and not len(vals.get('answer_choice_ids', [])) and \
# vals.get('type') not in ['descriptive_text', 'single_textbox', 'comment', 'table']:
# raise osv.except_osv(_('Warning!'), _('You must enter one or more answers for question "%s" of page %s .') % (vals['question'], page.title))
# if 'column_heading_ids' in vals and not len(vals.get('column_heading_ids', [])) and \
# vals.get('type') in ['matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'rating_scale']:
# raise osv.except_osv(_('Warning!'), _('You must enter one or more column headings for question "%s" of page %s.') % (vals['question'], page.title))
# if 'is_require_answer' in vals and vals.get('type') in ['multiple_choice_multiple_ans', 'matrix_of_choices_only_one_ans', \
# 'matrix_of_choices_only_multi_ans', 'rating_scale', 'multiple_textboxes', 'numerical_textboxes', 'date', 'date_and_time']:
# if vals.get('required_type') in ['at least', 'at most', 'exactly']:
# if 'answer_choice_ids' in vals and 'answer_choice_ids' in vals and vals.get('req_ans') > len(vals.get('answer_choice_ids', [])):
# raise osv.except_osv(_('Warning!'), _("#Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (len(vals['answer_choice_ids']) + 1))
# if vals.get('required_type') == 'a range':
# if 'answer_choice_ids' in vals:
# if not vals.get('minimum_req_ans') or vals['minimum_req_ans'] > len(vals['answer_choice_ids']):
# raise osv.except_osv(_('Warning!'), _("Minimum Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (len(vals['answer_choice_ids']) + 1))
# if not vals.get('maximum_req_ans') or vals['maximum_req_ans'] > len(vals['answer_choice_ids']):
# raise osv.except_osv(_('Warning!'), _("Maximum Required Answer you entered for your maximum is greater than the number of answer. Please use a number that is smaller than %d.") % (len(vals['answer_choice_ids']) + 1))
# if vals.get('maximum_req_ans', 0) <= vals.get('minimum_req_ans', 0):
# raise osv.except_osv(_('Warning!'), _("Maximum Required Answer is greater than Minimum Required Answer."))
# return super(survey_question, self).create(cr, uid, vals, context)
2010-01-20 14:28:28 +00:00
2010-11-22 10:37:53 +00:00
def survey_save ( self , cr , uid , ids , context = None ) :
2010-11-23 07:05:05 +00:00
if context is None :
2010-11-22 10:37:53 +00:00
context = { }
2013-10-21 09:03:19 +00:00
surv_name_wiz = self . pool . get ( ' survey.question.wiz ' )
2013-10-28 16:15:31 +00:00
surv_name_wiz . write ( cr , uid , [ context . get ( ' wizard_id ' , False ) ] ,
{ ' transfer ' : True , ' page_no ' : context . get ( ' page_number ' , False ) } )
2010-01-20 14:28:28 +00:00
return {
2010-04-06 07:02:31 +00:00
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' survey.question.wiz ' ,
' type ' : ' ir.actions.act_window ' ,
' target ' : ' new ' ,
' context ' : context
}
2010-01-20 14:28:28 +00:00
2013-11-29 09:27:32 +00:00
# Validation methods
def validate_question ( self , cr , uid , question , post , answer_tag , context = None ) :
2013-11-29 16:10:32 +00:00
''' Validate question, depending on question type and parameters '''
2013-11-29 09:27:32 +00:00
try :
checker = getattr ( self , ' validate_ ' + question . type )
except AttributeError :
2013-11-29 16:10:32 +00:00
_logger . warning ( question . type + " : This type of question has no validation method " )
2013-11-29 09:27:32 +00:00
return { }
else :
return checker ( cr , uid , question , post , answer_tag , context = context )
def validate_free_text ( self , cr , uid , question , post , answer_tag , context = None ) :
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 , cr , uid , question , post , answer_tag , context = None ) :
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 , cr , uid , question , post , answer_tag , context = None ) :
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 , cr , uid , question , post , answer_tag , context = None ) :
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 ( self , cr , uid , question , post , answer_tag , context = None ) :
errors = { }
if question . comments_allowed :
comment_tag = " %s _ %s " % ( answer_tag , question . comment_children_ids [ 0 ] . id )
# Empty answer to mandatory question
if question . constr_mandatory and not answer_tag in post :
errors . update ( { answer_tag : question . constr_error_msg } )
if question . constr_mandatory and answer_tag in post and post [ answer_tag ] . strip ( ) == ' ' :
errors . update ( { answer_tag : question . constr_error_msg } )
# Answer is a comment and is empty
if question . constr_mandatory and answer_tag in post and post [ answer_tag ] == " -1 " and question . comment_count_as_answer and comment_tag in post and not post [ comment_tag ] . strip ( ) :
errors . update ( { answer_tag : question . constr_error_msg } )
return errors
2013-12-09 12:25:15 +00:00
def validate_multiple_choice ( self , cr , uid , question , post , answer_tag , context = None ) :
errors = { }
if question . constr_mandatory :
answer_candidates = dict_keys_startswith ( post , answer_tag )
comment_flag = answer_candidates . pop ( ( " %s _ %s " % ( answer_tag , - 1 ) ) , None )
comment_answer = answer_candidates . pop ( ( " %s _ %s " % ( answer_tag , question . comment_children_ids [ 0 ] . id ) ) , ' ' ) . strip ( )
# There is no answer neither comments (if comments count as answer)
if not answer_candidates and question . comment_count_as_answer and comment_flag and comment_answer == ' ' :
errors . update ( { answer_tag : question . constr_error_msg } )
# There is no answer at all
if not answer_candidates and not comment_flag :
errors . update ( { answer_tag : question . constr_error_msg } )
return errors
2013-11-29 09:27:32 +00:00
2013-12-06 16:10:56 +00:00
def validate_matrix ( self , cr , uid , question , post , answer_tag , context = None ) :
errors = { }
if question . constr_mandatory :
lines_number = len ( question . labels_ids_2 )
2013-12-09 12:25:15 +00:00
answer_candidates = dict_keys_startswith ( post , answer_tag )
2013-12-12 11:04:35 +00:00
#comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, question.comment_children_ids[0].id)), None)
2013-12-09 12:25:15 +00:00
# Number of lines that have been answered
2013-12-06 16:10:56 +00:00
if question . matrix_subtype == ' simple ' :
2013-12-09 12:25:15 +00:00
answer_number = len ( answer_candidates )
elif question . matrix_subtype == ' multiple ' :
answer_number = len ( set ( [ sk . rsplit ( ' _ ' , 1 ) [ 0 ] for sk in answer_candidates . keys ( ) ] ) )
else :
raise RuntimeError ( " Invalid matrix subtype " )
# Validate lines
if question . constr_type == ' all ' and answer_number != lines_number :
errors . update ( { answer_tag : question . constr_error_msg } )
elif question . constr_type == ' at least ' and answer_number < question . constr_minimum_req_ans :
errors . update ( { answer_tag : question . constr_error_msg } )
elif question . constr_type == ' at most ' and answer_number > question . constr_maximum_req_ans :
errors . update ( { answer_tag : question . constr_error_msg } )
elif question . constr_type == ' exactly ' and answer_number != question . constr_maximum_req_ans :
errors . update ( { answer_tag : question . constr_error_msg } )
elif question . constr_type == ' a range ' and not ( question . constr_minimum_req_ans < = answer_number < = question . constr_maximum_req_ans ) :
errors . update ( { answer_tag : question . constr_error_msg } )
else :
pass # Everything is okay
2013-12-19 08:49:00 +00:00
return errors
2013-11-29 09:27:32 +00:00
2010-01-20 14:28:28 +00:00
2013-11-15 15:14:35 +00:00
class survey_label ( osv . osv ) :
2013-10-28 14:43:14 +00:00
''' A suggested answer for a question '''
2013-11-15 15:14:35 +00:00
_name = ' survey.label '
2013-10-28 14:43:14 +00:00
_rec_name = ' value '
2013-11-18 16:12:59 +00:00
_order = ' sequence '
2013-11-19 13:28:18 +00:00
_description = ' Survey Label '
2013-10-28 14:43:14 +00:00
_columns = {
' question_id ' : fields . many2one ( ' survey.question ' , ' Question ' ,
2013-11-26 12:53:03 +00:00
ondelete = ' cascade ' ) ,
' question_id_2 ' : fields . many2one ( ' survey.question ' , ' Question ' ,
ondelete = ' cascade ' ) ,
' sequence ' : fields . integer ( ' Label Sequence order ' ) ,
2013-11-21 11:04:57 +00:00
' value ' : fields . char ( " Suggested value " , translate = True ,
2013-10-28 16:15:31 +00:00
required = True )
2013-10-28 14:43:14 +00:00
}
2013-12-12 11:04:35 +00:00
defaults = {
2013-12-16 15:32:44 +00:00
' sequence ' : 100
2013-12-12 11:04:35 +00:00
}
2013-10-28 14:43:14 +00:00
2013-10-23 12:22:55 +00:00
class survey_user_input ( osv . osv ) :
2013-10-24 09:26:33 +00:00
''' Metadata for a set of one user ' s answers to a particular survey '''
2013-10-23 12:22:55 +00:00
_name = " survey.user_input "
2010-01-20 14:28:28 +00:00
_rec_name = ' date_create '
2013-11-19 13:28:18 +00:00
_description = ' Survey User Input '
2013-10-21 09:03:19 +00:00
2010-01-20 14:28:28 +00:00
_columns = {
2013-10-28 07:51:21 +00:00
' survey_id ' : fields . many2one ( ' survey.survey ' , ' Survey ' , required = True ,
2013-11-29 08:49:14 +00:00
readonly = 1 , ondelete = ' restrict ' ) ,
2013-10-25 09:56:54 +00:00
' date_create ' : fields . datetime ( ' Creation Date ' , required = True ,
2013-11-29 08:49:14 +00:00
readonly = 1 ) ,
2013-12-12 11:04:35 +00:00
' deadline ' : fields . datetime ( " Deadline " ,
help = " Date by which the person can open the survey and submit answers. \
Warning : " ,
2013-11-29 08:49:14 +00:00
oldname = " date_deadline " ) ,
2013-10-24 09:26:33 +00:00
' type ' : fields . selection ( [ ( ' manually ' , ' Manually ' ) , ( ' link ' , ' Link ' ) ] ,
2013-11-29 08:49:14 +00:00
' Answer Type ' , required = 1 , readonly = 1 ,
oldname = " response_type " ) ,
2013-10-24 09:26:33 +00:00
' state ' : fields . selection ( [ ( ' new ' , ' Not started yet ' ) ,
2013-11-29 08:49:14 +00:00
( ' skip ' , ' Partially completed ' ) ,
( ' done ' , ' Completed ' ) ] ,
' Status ' ,
readonly = True ) ,
2013-11-26 15:17:26 +00:00
' test_entry ' : fields . boolean ( ' Test entry ' , readonly = 1 ) ,
' token ' : fields . char ( " Identification token " , readonly = 1 , required = 1 ) ,
2013-10-24 09:26:33 +00:00
# Optional Identification data
2013-10-21 09:03:19 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Partner ' , readonly = 1 ) ,
2013-11-21 11:04:57 +00:00
' email ' : fields . char ( " E-mail " , readonly = 1 ) ,
2013-10-24 09:26:33 +00:00
2013-12-12 11:04:35 +00:00
# Displaying data
' last_displayed_page_id ' : fields . many2one ( ' survey.page ' ,
' Last displayed page ' ) ,
2013-10-24 09:26:33 +00:00
# The answers !
2013-11-28 07:44:18 +00:00
' user_input_line_ids ' : fields . one2many ( ' survey.user_input_line ' ,
2013-11-29 08:49:14 +00:00
' user_input_id ' , ' Answers ' ) ,
2010-01-28 07:32:59 +00:00
}
_defaults = {
2013-10-29 15:17:31 +00:00
' date_create ' : fields . datetime . now ,
2013-10-24 09:26:33 +00:00
' type ' : ' manually ' ,
' state ' : ' new ' ,
2013-10-29 15:17:31 +00:00
' token ' : lambda s , cr , uid , c : uuid . uuid4 ( ) . __str__ ( ) ,
2010-01-20 14:28:28 +00:00
}
[IMP]:survey,crm_hr,hr_evaluation.(Ref-YSA,APA)
-Rename Defination into Definition.
-When you click on test, answer or edit survey, remove the first screen with the selection box to select the survey.
-Add survey Types in demo data: Human Resources, Customer Feeback, Supplier Selection.
-In search view of surveys, remove the vertical separator between draft and open buttons, for type field set widget=selection.
-The button for responsible=my in the search view must be default="1".
-change demo data so that some surveys belongs to admin and some to demo user.
-The browse Response report must be renamed into "Print Answers".
-Browse the answer in the screen instead of printing a PDF.
-In the history one2many, first put the date and then the user.
-I started a survey but did not finished it. so that's normal but I was not able to restart the survey (it says I already answered).
-Removed the "Que: " prefix in each report.
bzr revid: apa@tinyerp.com-20100223093223-vwhgkn8kmdo8e6mo
2010-02-23 09:32:23 +00:00
2013-11-20 16:15:19 +00:00
_sql_constraints = [
( ' unique_token ' , ' UNIQUE (token) ' , ' A token must be unique! ' )
]
2013-11-26 12:53:03 +00:00
def create ( self , cr , uid , vals , context = None ) :
#if not vals['test_entry']:
# if survey_obj.exists(cr, uid, survey.id, context=context) == []:
# return werkzeug.utils.redirect("/survey/")
# # In case of auth required, block public user
2013-12-06 09:17:44 +00:00
# if survey.auth_required and uid == request.registry['website'].get_public_user(request.cr, SUPERUSER_ID, request.context):
2013-11-26 12:53:03 +00:00
# return request.website.render("website.401")
# # In case of non open surveys
# if survey.state != 'open':
# return request.website.render("survey.notopen")
# # If enough surveys completed
# if survey.user_input_limit > 0:
# completed = user_input_obj.search(cr, uid, [('state', '=', 'done')], count=True)
# if completed >= survey.user_input_limit:
# return request.website.render("survey.notopen")
# # Manual surveying
# if not token:
# if survey.visible_to_user:
# user_input_id = user_input_obj.create(cr, uid, {'survey_id': survey.id})
# user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
# else: # An user cannot open hidden surveys without token
# return request.website.render("website.403")
# else:
# try:
# user_input_id = user_input_obj.search(cr, uid, [('token', '=', token)])[0]
# except IndexError: # Invalid token
# return request.website.render("website.403")
# else:
# user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
return super ( survey_user_input , self ) . create ( cr , uid , vals , context )
# raise osv.except_osv(_('Warning!'), _('You must enter one or more answers for question "%s" of page %s .') % (vals['question'], page.title))
2013-11-21 11:04:57 +00:00
def do_clean_emptys ( self , cr , uid , automatic = False , context = None ) :
2013-11-29 16:10:32 +00:00
''' Remove empty user inputs that have been created manually
. . note :
This function does not remove
'''
2013-11-29 08:49:14 +00:00
empty_user_input_ids = self . search ( cr , uid , [ ( ' type ' , ' = ' , ' manually ' ) ,
2013-11-29 16:10:32 +00:00
( ' state ' , ' = ' , ' new ' ) ,
( ' date_create ' , ' < ' , ( datetime . datetime . now ( ) - datetime . timedelta ( hours = 1 ) ) . strftime ( DF ) ) ] ,
2013-11-29 08:49:14 +00:00
context = context )
2013-11-21 11:04:57 +00:00
if empty_user_input_ids :
2013-11-29 08:49:14 +00:00
self . unlink ( cr , uid , empty_user_input_ids , context = context )
2013-10-21 09:03:19 +00:00
def action_survey_resent ( self , cr , uid , ids , context = None ) :
record = self . browse ( cr , uid , ids [ 0 ] , context = context )
context = context or { }
context . update ( {
' survey_resent_token ' : True ,
' default_partner_ids ' : record . partner_id and [ record . partner_id . id ] or [ ] ,
' default_multi_email ' : record . email or " " ,
' default_public ' : ' email_private ' ,
} )
2013-10-28 16:15:31 +00:00
return self . pool . get ( ' survey.survey ' ) . action_survey_sent ( cr , uid ,
[ record . survey_id . id ] , context = context )
2013-10-21 09:03:19 +00:00
def action_print_response ( self , cr , uid , ids , context = None ) :
"""
Print Survey Answer in pdf format .
@return : Dictionary value for created survey answer report
"""
return {
' type ' : ' ir.actions.report.xml ' ,
' report_name ' : ' survey.browse.response ' ,
' datas ' : {
' model ' : ' survey.print.statistics ' ,
' form ' : {
2013-10-28 16:15:31 +00:00
' response_ids ' : ids ,
' orientation ' : ' vertical ' ,
' paper_size ' : ' letter ' ,
' page_number ' : 0 ,
' without_pagebreak ' : 0
2013-10-21 09:03:19 +00:00
}
} ,
}
def action_preview ( self , cr , uid , ids , context = None ) :
"""
Get preview response
"""
context = context or { }
2013-10-28 07:51:21 +00:00
self . pool . get ( ' survey.survey ' ) . check_access_rights ( cr , uid , ' write ' )
2013-10-21 09:03:19 +00:00
record = self . browse ( cr , uid , ids [ 0 ] , context = context )
context . update ( {
' ir_actions_act_window_target ' : ' new ' ,
' survey_id ' : record . survey_id . id ,
' response_id ' : ids [ 0 ] ,
' readonly ' : True ,
} )
return {
' view_type ' : ' form ' ,
2013-12-06 09:17:44 +00:00
' view_mode ' : ' form ' ,
2013-10-21 09:03:19 +00:00
' res_model ' : ' survey.question.wiz ' ,
' type ' : ' ir.actions.act_window ' ,
' target ' : ' new ' ,
' context ' : context
}
2010-11-23 11:31:52 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
2013-10-28 16:15:31 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' You cannot duplicate this \
element ! ' ))
2013-10-23 09:03:42 +00:00
2010-01-20 14:28:28 +00:00
2013-10-24 09:26:33 +00:00
class survey_user_input_line ( osv . osv ) :
2013-11-28 07:44:18 +00:00
_name = ' survey.user_input_line '
2013-11-19 13:28:18 +00:00
_description = ' Survey User Input Line '
2010-01-20 14:28:28 +00:00
_rec_name = ' date_create '
_columns = {
2013-10-24 09:26:33 +00:00
' user_input_id ' : fields . many2one ( ' survey.user_input ' , ' User Input ' ,
2013-11-29 08:49:14 +00:00
ondelete = ' cascade ' , required = 1 ) ,
2013-10-24 09:26:33 +00:00
' question_id ' : fields . many2one ( ' survey.question ' , ' Question ' ,
2013-11-29 08:49:14 +00:00
ondelete = ' restrict ' ) ,
2013-11-25 08:04:46 +00:00
' page_id ' : fields . related ( ' question_id ' , ' page_id ' , type = ' many2one ' ,
2013-11-29 08:49:14 +00:00
relation = ' survey.page ' , string = " Page " ) ,
2013-11-28 07:44:18 +00:00
' survey_id ' : fields . related ( ' user_input_id ' , ' survey_id ' ,
2013-11-29 08:49:14 +00:00
type = " many2one " , relation = " survey.survey " ,
string = ' Survey ' ) ,
2013-12-12 11:04:35 +00:00
' date_create ' : fields . datetime ( ' Create Date ' , required = 1 ) ,
2013-11-25 08:04:46 +00:00
' skipped ' : fields . boolean ( ' Skipped ' ) ,
2013-11-28 07:44:18 +00:00
' answer_type ' : fields . selection ( [ ( ' text ' , ' Text ' ) ,
2013-11-29 08:49:14 +00:00
( ' number ' , ' Number ' ) ,
( ' date ' , ' Date ' ) ,
( ' free_text ' , ' Free Text ' ) ,
( ' suggestion ' , ' Suggestion ' ) ] ,
' Answer Type ' ) ,
2013-10-28 14:43:14 +00:00
' value_text ' : fields . char ( " Text answer " ) ,
' value_number ' : fields . float ( " Numerical answer " ) ,
' value_date ' : fields . datetime ( " Date answer " ) ,
' value_free_text ' : fields . text ( " Free Text answer " ) ,
2013-12-16 15:32:44 +00:00
' value_suggested ' : fields . many2one ( ' survey.label ' , " Suggested answer " ) ,
' value_suggested_row ' : fields . many2one ( ' survey.label ' , " Row answer " ) ,
2013-10-24 09:26:33 +00:00
}
_defaults = {
' skipped ' : False ,
2013-12-12 11:04:35 +00:00
' date_create ' : fields . datetime . now ( )
2010-01-20 14:28:28 +00:00
}
2013-11-29 16:10:32 +00:00
def save_lines ( self , cr , uid , user_input_id , question , post , answer_tag ,
context = None ) :
''' Save answers to questions, depending on question type
If an answer already exists for question and user_input_id , it will be
overwritten ( in order to maintain data consistency ) . '''
try :
saver = getattr ( self , ' save_line_ ' + question . type )
except AttributeError :
_logger . error ( question . type + " : This type of question has no saving function " )
return False
else :
saver ( cr , uid , user_input_id , question , post , answer_tag , context = context )
def save_line_free_text ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
2013-12-18 07:20:04 +00:00
' skipped ' : False
2013-11-29 16:10:32 +00:00
}
2013-12-12 11:04:35 +00:00
if answer_tag in post and post [ answer_tag ] . strip ( ) != ' ' :
2013-11-29 16:10:32 +00:00
vals . update ( { ' answer_type ' : ' free_text ' , ' value_free_text ' : post [ answer_tag ] } )
else :
2013-12-16 15:32:44 +00:00
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
2013-11-29 16:10:32 +00:00
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . write ( cr , uid , old_uil [ 0 ] , vals , context = context )
else :
self . create ( cr , uid , vals , context = context )
return True
def save_line_textbox ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
2013-12-18 07:20:04 +00:00
' skipped ' : False
2013-11-29 16:10:32 +00:00
}
2013-12-12 11:04:35 +00:00
if answer_tag in post and post [ answer_tag ] . strip ( ) != ' ' :
2013-11-29 16:10:32 +00:00
vals . update ( { ' answer_type ' : ' text ' , ' value_text ' : post [ answer_tag ] } )
else :
2013-12-16 15:32:44 +00:00
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
2013-11-29 16:10:32 +00:00
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . write ( cr , uid , old_uil [ 0 ] , vals , context = context )
else :
self . create ( cr , uid , vals , context = context )
return True
def save_line_numerical_box ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
2013-12-18 07:20:04 +00:00
' skipped ' : False
2013-11-29 16:10:32 +00:00
}
2013-12-12 11:04:35 +00:00
if answer_tag in post and post [ answer_tag ] . strip ( ) != ' ' :
2013-11-29 16:10:32 +00:00
vals . update ( { ' answer_type ' : ' number ' , ' value_number ' : float ( post [ answer_tag ] ) } )
else :
2013-12-16 15:32:44 +00:00
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
2013-11-29 16:10:32 +00:00
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . write ( cr , uid , old_uil [ 0 ] , vals , context = context )
else :
self . create ( cr , uid , vals , context = context )
return True
def save_line_datetime ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
2013-12-18 07:20:04 +00:00
' skipped ' : False
2013-11-29 16:10:32 +00:00
}
2013-12-16 07:37:43 +00:00
if answer_tag in post and post [ answer_tag ] . strip ( ) != ' ' :
2013-12-06 16:10:56 +00:00
vals . update ( { ' answer_type ' : ' date ' , ' value_date ' : post [ answer_tag ] } )
else :
2013-12-16 15:32:44 +00:00
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
2013-12-06 16:10:56 +00:00
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . write ( cr , uid , old_uil [ 0 ] , vals , context = context )
else :
self . create ( cr , uid , vals , context = context )
return True
2013-12-16 07:37:43 +00:00
def save_line_simple_choice ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
2013-12-18 07:20:04 +00:00
' skipped ' : False
2013-12-16 07:37:43 +00:00
}
if answer_tag in post and post [ answer_tag ] . strip ( ) != ' ' :
2013-12-16 15:32:44 +00:00
vals . update ( { ' answer_type ' : ' suggestion ' , ' value_suggested ' : post [ answer_tag ] } )
2013-12-16 07:37:43 +00:00
else :
2013-12-16 15:32:44 +00:00
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
2013-12-16 07:37:43 +00:00
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . write ( cr , uid , old_uil [ 0 ] , vals , context = context )
else :
self . create ( cr , uid , vals , context = context )
return True
2013-11-29 16:10:32 +00:00
2013-12-18 16:14:19 +00:00
def save_line_multiple_choice ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
' skipped ' : False
}
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . unlink ( cr , uid , old_uil , context = context )
ca = dict_keys_startswith ( post , answer_tag )
if len ( ca ) > 0 :
for a in ca :
vals . update ( { ' answer_type ' : ' suggestion ' , ' value_suggested ' : ca [ a ] } )
self . create ( cr , uid , vals , context = context )
else :
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
self . create ( cr , uid , vals , context = context )
return True
2013-11-29 16:10:32 +00:00
2013-12-19 14:00:24 +00:00
def save_line_matrix ( self , cr , uid , user_input_id , question , post , answer_tag , context = None ) :
vals = {
' user_input_id ' : user_input_id ,
' question_id ' : question . id ,
' page_id ' : question . page_id . id ,
' survey_id ' : question . survey_id . id ,
' skipped ' : False
}
old_uil = self . search ( cr , uid , [ ( ' user_input_id ' , ' = ' , user_input_id ) ,
( ' survey_id ' , ' = ' , question . survey_id . id ) ,
( ' question_id ' , ' = ' , question . id ) ] ,
context = context )
if old_uil :
self . unlink ( cr , uid , old_uil , context = context )
ca = dict_keys_startswith ( post , answer_tag )
no_answers = True
2013-12-24 08:46:49 +00:00
if question . matrix_subtype == ' simple ' :
2013-12-19 14:00:24 +00:00
for row in question . labels_ids_2 :
2013-12-24 08:46:49 +00:00
a_tag = " %s _ %s " % ( answer_tag , row . id )
2013-12-19 14:00:24 +00:00
if a_tag in ca :
no_answers = False
2013-12-24 08:46:49 +00:00
vals . update ( { ' answer_type ' : ' suggestion ' , ' value_suggested ' : ca [ a_tag ] , ' value_suggested_row ' : row . id } )
2013-12-19 14:00:24 +00:00
self . create ( cr , uid , vals , context = context )
2013-12-24 08:46:49 +00:00
elif question . matrix_subtype == ' multiple ' :
for col in question . labels_ids :
for row in question . labels_ids_2 :
a_tag = " %s _ %s _ %s " % ( answer_tag , row . id , col . id )
if a_tag in ca :
no_answers = False
vals . update ( { ' answer_type ' : ' suggestion ' , ' value_suggested ' : col . id , ' value_suggested_row ' : row . id } )
self . create ( cr , uid , vals , context = context )
2013-12-19 14:00:24 +00:00
if no_answers :
vals . update ( { ' answer_type ' : None , ' skipped ' : True } )
self . create ( cr , uid , vals , context = context )
return True
2013-12-18 07:20:04 +00:00
2013-11-29 16:10:32 +00:00
def dict_keys_startswith ( dictionary , string ) :
2013-11-29 08:49:14 +00:00
''' 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 ( ) ) }
2013-10-21 09:03:19 +00:00
# vim: exp and tab: smartindent: tabstop=4: softtabstop=4: shiftwidth=4: