[ADD] Survey : Module added from trunk extra addons with new menu structure
[odoo/odoo.git] / addons / survey / survey.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from osv import fields, osv
24 import datetime
25 from time import strftime
26 import datetime
27 import copy
28 from tools.translate import _
29 from lxml import etree
30 from tools import to_xml
31 import tools
32
33 class survey(osv.osv):
34     _name = 'survey'
35     _description = 'Survey'
36     _rec_name = 'title'
37     _columns = {
38         'title' : fields.char('Survey Title', size=128, required=1),
39         'page_ids' : fields.one2many('survey.page', 'survey_id', 'Page'),
40         'date_open' : fields.datetime('Survey Open Date', readonly=1),
41         'date_close' : fields.datetime('Survey Close Date', readonly=1),
42         'max_response_limit' : fields.integer('Maximum Response Limit'),
43         'response_user' : fields.integer('Maximum Response per User',
44                      help="Set to one if  you require only one response per user"),
45         'state' : fields.selection([('draft', 'Draft'), ('open', 'Open'), ('close', 'Closed'), ('cancel', 'Cancelled')], 'Status', readonly=True),
46         'responsible_id' : fields.many2one('res.users', 'Responsible'),
47         'tot_start_survey' : fields.integer("Total Started Survey", readonly=1),
48         'tot_comp_survey' : fields.integer("Total Completed Survey", readonly=1),
49         'note' : fields.text('Description', size=128),
50         'history' : fields.one2many('survey.history', 'survey_id', 'History Lines', readonly=True),
51         'users': fields.many2many('res.users', 'survey_users_rel', 'sid', 'uid', 'Users'),
52         'question_prefix' : fields.char('Question Prefix', size=128, required=1),
53     }
54     _defaults = {
55         'state' : lambda * a: "draft",
56         'tot_start_survey' : lambda * a: 0,
57         'tot_comp_survey' : lambda * a: 0,
58         'question_prefix' : lambda * a: "Que ",
59     }
60
61     def survey_draft(self, cr, uid, ids, arg):
62         self.write(cr, uid, ids, { 'state' : 'draft'})
63         return True
64
65     def survey_open(self, cr, uid, ids, arg):
66         self.write(cr, uid, ids, { 'state' : 'open', 'date_open':strftime("%Y-%m-%d %H:%M:%S")})
67         return True
68
69     def survey_close(self, cr, uid, ids, arg):
70         self.write(cr, uid, ids, { 'state' : 'close', 'date_close':strftime("%Y-%m-%d %H:%M:%S") })
71         return True
72
73     def survey_cancel(self, cr, uid, ids, arg):
74         self.write(cr, uid, ids, { 'state' : 'cancel' })
75         return True
76
77 survey()
78
79 class survey_history(osv.osv):
80     _name = 'survey.history'
81     _description = 'Survey History'
82     _rec_name = 'date'
83     _columns = {
84         'survey_id' : fields.many2one('survey', 'Survey'),
85         'user_id' : fields.many2one('res.users', 'User', readonly=True),
86         'date' : fields.datetime('Date started', readonly=1),
87     }
88     _defaults = {
89          'date' : lambda * a: datetime.datetime.now()
90     }
91
92 survey_history()
93
94 class survey_page(osv.osv):
95     _name = 'survey.page'
96     _description = 'Survey Pages'
97     _rec_name = 'title'
98     _order = 'sequence'
99     _columns = {
100         'title' : fields.char('Page Title', size=128, required=1),
101         'survey_id' : fields.many2one('survey', 'Survey', ondelete='cascade'),
102         'question_ids' : fields.one2many('survey.question', 'page_id', 'Question'),
103         'sequence' : fields.integer('Page Nr'),
104         'note' : fields.text('Description'),
105     }
106     _defaults = {
107         'sequence' : lambda * a: 1
108     }
109     def default_get(self, cr, uid, fields, context={}):
110         data = super(survey_page, self).default_get(cr, uid, fields, context)
111         if context.has_key('line_order') and context['line_order']:
112             if len(context['line_order'][-1]) > 2 and context['line_order'][-1][2].has_key('sequence'):
113                 data['sequence'] = context['line_order'][-1][2]['sequence'] + 1
114         return data
115 survey_page()
116
117 class survey_question(osv.osv):
118     _name = 'survey.question'
119     _description = 'Survey Question'
120     _rec_name = 'question'
121     _order = 'sequence'
122
123     def _calc_response(self, cr, uid, ids, field_name, arg, context):
124         val = {}
125         cr.execute("select question_id, count(id) as Total_response from survey_response_line where state='done' and question_id in (%s) group by question_id" % ",".join(map(str, map(int, ids))))
126         ids1 = copy.deepcopy(ids)
127         for rec in  cr.fetchall():
128             ids1.remove(rec[0])
129             val[rec[0]] = int(rec[1])
130         for id in ids1:
131             val[id] = 0
132         return val
133
134     _columns = {
135         'page_id' : fields.many2one('survey.page', 'Survey Page', ondelete='cascade', required=1),
136         'question' :  fields.char('Question', size=128, required=1),
137         'answer_choice_ids' : fields.one2many('survey.answer', 'question_id', 'Answer'),
138         'response_ids' : fields.one2many('survey.response.line', 'question_id', 'Response', readonly=1),
139         'is_require_answer' : fields.boolean('Required Answer'),
140         'required_type' : fields.selection([('',''), ('all','All'), ('at least','At Least'), ('at most','At Most'), ('exactly','Exactly'), ('a range','A Range')], 'Respondent must answer'),
141         'req_ans' : fields.integer('#Required Answer'),
142         'maximum_req_ans' : fields.integer('Maximum Required Answer'),
143         'minimum_req_ans' : fields.integer('Minimum Required Answer'),
144         'req_error_msg' : fields.text('Error Message'),
145         'allow_comment' : fields.boolean('Allow Comment Field'),
146         'sequence' : fields.integer('Sequence'),
147         'tot_resp' : fields.function(_calc_response, method=True, string="Total Response"),
148         'survey' : fields.related('page_id', 'survey_id', type='many2one', relation='survey', string='Survey'),
149         'descriptive_text' : fields.text('Descriptive Text', size=255),
150         'column_heading_ids' : fields.one2many('survey.question.column.heading', 'question_id',' Column heading'),
151         'column_ids' : fields.one2many('survey.tbl.column.heading', 'question_id',' Column'),
152         'type' : fields.selection([('multiple_choice_only_one_ans','Multiple Choice (Only One Answer)'),
153                                      ('multiple_choice_multiple_ans','Multiple Choice (Multiple Answer)'),
154                                      ('matrix_of_choices_only_one_ans','Matrix of Choices (Only One Answers Per Row)'),
155                                      ('matrix_of_choices_only_multi_ans','Matrix of Choices (Multiple Answers Per Row)'),
156                                      ('matrix_of_drop_down_menus','Matrix of Drop-down Menus'),
157                                      ('rating_scale','Rating Scale'),('single_textbox','Single Textbox'),
158                                      ('multiple_textboxes','Multiple Textboxes'),('comment','Comment/Essay Box'),
159                                      ('numerical_textboxes','Numerical Textboxes'),('date','Date'),
160                                      ('date_and_time','Date and Time'),('descriptive_text','Descriptive Text'),
161                                      ('table','Table'),
162                                     ], 'Question Type',  required=1,),
163         'comment_label' : fields.char('Field Label', size = 255),
164         'comment_field_type' : fields.selection([('',''),('char', 'Single Line Of Text'), ('text', 'Paragraph of Text')], 'Comment Field Type'),
165         'comment_valid_type' : fields.selection([('do_not_validate', '''Don't Validate Comment Text.'''),
166                                                  ('must_be_specific_length', 'Must Be Specific Length'),
167                                                  ('must_be_whole_number', 'Must Be A Whole Number'),
168                                                  ('must_be_decimal_number', 'Must Be A Decimal Number'),
169                                                  ('must_be_date', 'Must Be A Date'),
170                                                  ('must_be_email_address', 'Must Be An Email Address'),
171                                                  ], 'Text Validation'),
172         'comment_minimum_no' : fields.integer('Minimum number'),
173         'comment_maximum_no' : fields.integer('Maximum number'),
174         'comment_minimum_float' : fields.float('Minimum decimal number'),
175         'comment_maximum_float' : fields.float('Maximum decimal number'),
176         'comment_minimum_date' : fields.date('Minimum date'),
177         'comment_maximum_date' : fields.date('Maximum date'),
178         'comment_valid_err_msg' : fields.text('Error message'),
179         'make_comment_field' : fields.boolean('Make Comment Field an Answer Choice'),
180         'make_comment_field_err_msg' : fields.text('Error message'),
181         'validation_type' : fields.selection([('do_not_validate', '''Don't Validate Comment Text.'''),\
182                                                  ('must_be_specific_length', 'Must Be Specific Length'),\
183                                                  ('must_be_whole_number', 'Must Be A Whole Number'),\
184                                                  ('must_be_decimal_number', 'Must Be A Decimal Number'),\
185                                                  ('must_be_date', 'Must Be A Date'),\
186                                                  ('must_be_email_address', 'Must Be An Email Address')\
187                                                  ], 'Text Validation'),
188         'validation_minimum_no' : fields.integer('Minimum number'),
189         'validation_maximum_no' : fields.integer('Maximum number'),
190         'validation_minimum_float' : fields.float('Minimum decimal number'),
191         'validation_maximum_float' : fields.float('Maximum decimal number'),
192         'validation_minimum_date' : fields.date('Minimum date'),
193         'validation_maximum_date' : fields.date('Maximum date'),
194         'validation_valid_err_msg' : fields.text('Error message'),
195         'numeric_required_sum' : fields.integer('Sum of all choices'),
196         'numeric_required_sum_err_msg' : fields.text('Error message'),
197         'rating_allow_one_column_require' : fields.boolean('Allow Only One Response per Column (Forced Ranking)'),
198         'in_visible_rating_weight':fields.boolean('Is Rating Scale Invisible?'),
199         'in_visible_menu_choice':fields.boolean('Is Menu Choice Invisible?'),
200         'comment_column':fields.boolean('Add comment column in matrix'),
201         'column_name':fields.char('Column Name',size=256),
202         'no_of_rows' : fields.integer('No of Rows'),
203     }
204     _defaults = {
205          'sequence' : lambda * a: 1,
206          'type' : lambda * a: 'multiple_choice_multiple_ans',
207          'req_error_msg' : lambda * a: 'This question requires an answer.',
208          'required_type' : lambda * a: '',
209          'comment_label' : lambda * a: 'Other',
210          'comment_valid_type' : lambda * a: 'do_not_validate',
211          'comment_valid_err_msg' : lambda * a : 'The comment you entered is in an invalid format.',
212          'validation_type' : lambda * a: 'do_not_validate',
213          'validation_valid_err_msg' : lambda * a : 'The comment you entered is in an invalid format.',
214          'numeric_required_sum_err_msg' : lambda * a :'The choices need to add up to [enter sum here].',
215          'make_comment_field_err_msg' : lambda * a : 'Please enter a comment.',
216     }
217
218     def on_change_type(self, cr, uid, ids, type, context=None):
219         if type in ['rating_scale']:
220             return {'value': {'in_visible_rating_weight':False,'in_visible_menu_choice':True}}
221         elif type in ['matrix_of_drop_down_menus']:
222             return {'value': {'in_visible_rating_weight':True,'in_visible_menu_choice':False}}
223         elif type in ['single_textbox']:
224             return {'value': {'in_visible_rating_weight':True,'in_visible_menu_choice':True}}
225         else:
226             return {'value': {'in_visible_rating_weight':True,'in_visible_menu_choice':True}}
227
228     def write(self, cr, uid, ids, vals, context=None):
229         questions = self.read(cr,uid, ids, ['answer_choice_ids', 'type', 'required_type','req_ans', 'minimum_req_ans', 'maximum_req_ans', 'column_heading_ids'])
230         for question in questions:
231             col_len = len(question['column_heading_ids'])
232             if vals.has_key('column_heading_ids'):
233                 for col in vals['column_heading_ids']:
234                     if type(col[2]) == type({}):
235                         col_len += 1
236                     else:
237                         col_len -= 1
238             if vals.has_key('type'):
239                 que_type = vals['type']
240             else:
241                 que_type = question['type']
242             if que_type in ['matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'matrix_of_drop_down_menus', 'rating_scale']:
243                 if not col_len:
244                     raise osv.except_osv(_('Error !'),_("You must enter one or more column heading."))
245
246             ans_len = len(question['answer_choice_ids'])
247             if vals.has_key('answer_choice_ids'):
248                 for ans in vals['answer_choice_ids']:
249                     if type(ans[2]) == type({}):
250                         ans_len += 1
251                     else:
252                         ans_len -= 1
253             if que_type not in ['descriptive_text', 'single_textbox', 'comment','table']:
254                 if not ans_len:
255                     raise osv.except_osv(_('Error !'),_("You must enter one or more Answer."))
256
257             req_type = ""
258             if vals.has_key('required_type'):
259                 req_type = vals['required_type']
260             else:
261                 req_type = question['required_type']
262             if req_type in ['at least', 'at most', 'exactly']:
263                 if vals.has_key('req_ans'):
264                     if not vals['req_ans'] or  vals['req_ans'] > ans_len:
265                         raise osv.except_osv(_('Error !'),_("#Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (ans_len + 1))
266                 else:
267                     if not question['req_ans'] or  question['req_ans'] > ans_len:
268                         raise osv.except_osv(_('Error !'),_("#Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (ans_len + 1))
269
270             if req_type == 'a range':
271                 minimum_ans = 0
272                 maximum_ans = 0
273                 if vals.has_key('minimum_req_ans'):
274                     minimum_ans = vals['minimum_req_ans']
275                     if not vals['minimum_req_ans'] or  vals['minimum_req_ans'] > ans_len:
276                         raise osv.except_osv(_('Error !'),_("Minimum Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (ans_len + 1))
277                 else:
278                     minimum_ans = question['minimum_req_ans']
279                     if not question['minimum_req_ans'] or  question['minimum_req_ans'] > ans_len:
280                         raise osv.except_osv(_('Error !'),_("Minimum Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (ans_len + 1))
281                 if vals.has_key('maximum_req_ans'):
282                     maximum_ans = vals['maximum_req_ans']
283                     if not vals['maximum_req_ans'] or vals['maximum_req_ans'] > ans_len:
284                         raise osv.except_osv(_('Error !'),_("Maximum Required Answer you entered for your maximum is greater than the number of answer. Please use a number that is smaller than %d.") % (ans_len + 1))
285                 else:
286                     maximum_ans = question['maximum_req_ans']
287                     if not question['maximum_req_ans'] or question['maximum_req_ans'] > ans_len:
288                         raise osv.except_osv(_('Error !'),_("Maximum Required Answer you entered for your maximum is greater than the number of answer. Please use a number that is smaller than %d.") % (ans_len + 1))
289                 if maximum_ans <= minimum_ans:
290                     raise osv.except_osv(_('Error !'),_("Maximum Required Answer is greater than Minimum Required Answer"))
291             if question['type'] ==  'matrix_of_drop_down_menus' and vals.has_key('column_heading_ids'):
292                 for col in vals['column_heading_ids']:
293                     if col[2].has_key('menu_choice') and not col[2]['menu_choice']:
294                         raise osv.except_osv(_('Error !'),_("You must enter one or more menu choices in column heading"))
295                     elif col[2].has_key('menu_choice') and col[2]['menu_choice'].strip() == '':
296                         raise osv.except_osv(_('Error !'),_("You must enter one or more menu choices in column heading (white spaces not allowed)"))
297         return super(survey_question, self).write(cr, uid, ids, vals, context=context)
298
299     def create(self, cr, uid, vals, context):
300         minimum_ans = 0
301         maximum_ans = 0
302         if vals.has_key('answer_choice_ids') and  not len(vals['answer_choice_ids']):
303             if vals.has_key('type') and vals['type'] not in ['descriptive_text', 'single_textbox', 'comment','table']:
304                 raise osv.except_osv(_('Error !'),_("You must enter one or more answer."))
305         if vals.has_key('column_heading_ids') and  not len(vals['column_heading_ids']):
306             if vals.has_key('type') and vals['type'] in ['matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'matrix_of_drop_down_menus', 'rating_scale']:
307                 raise osv.except_osv(_('Error !'),_("You must enter one or more column heading."))
308         if vals.has_key('required_type') and vals['required_type'] in ['at least', 'at most', 'exactly']:
309             if vals['req_ans'] > len(vals['answer_choice_ids']) or not vals['req_ans']:
310                 raise osv.except_osv(_('Error !'),_("#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))
311         if vals.has_key('required_type') and vals['required_type'] == 'a range':
312             minimum_ans = vals['minimum_req_ans']
313             maximum_ans = vals['maximum_req_ans']
314             if vals['minimum_req_ans'] > len(vals['answer_choice_ids']) or not vals['minimum_req_ans']:
315                 raise osv.except_osv(_('Error !'),_("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))
316             if vals['maximum_req_ans'] > len(vals['answer_choice_ids']) or not vals['maximum_req_ans']:
317                 raise osv.except_osv(_('Error !'),_("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))
318             if maximum_ans <= minimum_ans:
319                 raise osv.except_osv(_('Error !'),_("Maximum Required Answer is greater than Minimum Required Answer"))
320         if vals['type'] ==  'matrix_of_drop_down_menus':
321             for col in vals['column_heading_ids']:
322                 if not col[2]['menu_choice']:
323                     raise osv.except_osv(_('Error !'),_("You must enter one or more menu choices in column heading"))
324                 elif col[2]['menu_choice'].strip() == '':
325                     raise osv.except_osv(_('Error !'),_("You must enter one or more menu choices in column heading (white spaces not allowed)"))
326
327         res = super(survey_question, self).create(cr, uid, vals, context)
328         return res
329
330     def default_get(self, cr, uid, fields, context={}):
331         data = super(survey_question, self).default_get(cr, uid, fields, context)
332         if context.has_key('line_order') and context['line_order']:
333             if len(context['line_order'][-1]) > 2 and context['line_order'][-1][2].has_key('sequence'):
334                 data['sequence'] = context['line_order'][-1][2]['sequence'] + 1
335         return data
336
337 survey_question()
338
339
340 class survey_tbl_column_heading(osv.osv):
341     _name = 'survey.tbl.column.heading'
342     _columns = {
343         'name' : fields.char('Column', size=255),
344         'question_id' : fields.many2one('survey.question', 'Question', ondelete='cascade'),
345     }
346 survey_tbl_column_heading()
347
348 class survey_question_column_heading(osv.osv):
349     _name = 'survey.question.column.heading'
350     _description = 'Survey Question Column Heading'
351     _rec_name = 'title'
352
353     def _get_in_visible_rating_weight(self,cr, uid, context={}):
354         if context.get('in_visible_rating_weight',False):
355             return context['in_visible_rating_weight']
356         return False
357     def _get_in_visible_menu_choice(self,cr, uid, context={}):
358         if context.get('in_visible_menu_choice',False):
359             return context['in_visible_menu_choice']
360         return False
361
362     _columns = {
363         'title' : fields.char('Column Heading', size=128, required=1),
364         'menu_choice' : fields.text('Menu Choice'),
365         'rating_weight' : fields.integer('Weight'),
366         'question_id' : fields.many2one('survey.question', 'Question', ondelete='cascade'),
367         'in_visible_rating_weight':fields.boolean('Is Rating Scale Invisible ??'),
368         'in_visible_menu_choice':fields.boolean('Is Menu Choice Invisible??')
369     }
370     _defaults={
371        'in_visible_rating_weight':_get_in_visible_rating_weight,
372        'in_visible_menu_choice':_get_in_visible_menu_choice,
373     }
374 survey_question_column_heading()
375
376 class survey_answer(osv.osv):
377     _name = 'survey.answer'
378     _description = 'Survey Answer'
379     _rec_name = 'answer'
380     _order = 'sequence'
381
382     def _calc_response_avg(self, cr, uid, ids, field_name, arg, context):
383         val = {}
384         for rec in self.browse(cr, uid, ids):
385
386             cr.execute("select count(question_id) ,(select count(answer_id) \
387                 from survey_response_answer sra, survey_response_line sa \
388                 where sra.response_id = sa.id and sra.answer_id = %d \
389                 and sa.state='done') as tot_ans from survey_response_line \
390                 where question_id = %d and state = 'done'"\
391                      % (rec.id, rec.question_id.id))
392
393             res = cr.fetchone()
394             if res[0]:
395                 avg = float(res[1]) * 100 / res[0]
396             else:
397                 avg = 0.0
398             val[rec.id] = {
399                 'response': res[1],
400                 'average': round(avg, 2),
401             }
402         return val
403
404     _columns = {
405         'question_id' : fields.many2one('survey.question', 'Question', ondelete='cascade'),
406         'answer' : fields.char('Answer', size=128, required=1),
407         'sequence' : fields.integer('Sequence'),
408         'response' : fields.function(_calc_response_avg, method=True, string="#Response", multi='sums'),
409         'average' : fields.function(_calc_response_avg, method=True, string="#Avg", multi='sums'),
410     }
411     _defaults = {
412          'sequence' : lambda * a: 1
413     }
414
415     def default_get(self, cr, uid, fields, context={}):
416         data = super(survey_answer, self).default_get(cr, uid, fields, context)
417         if context.has_key('line_order') and context['line_order']:
418             if len(context['line_order'][-1]) > 2 and context['line_order'][-1][2].has_key('sequence'):
419                 data['sequence'] = context['line_order'][-1][2]['sequence'] + 1
420         return data
421
422 survey_answer()
423
424 class survey_response(osv.osv):
425     _name = "survey.response"
426     _rec_name = 'date_create'
427     _columns = {
428         'survey_id' : fields.many2one('survey', 'Survey', required=1),
429         'date_create' : fields.datetime('Create Date', required=1),
430         'user_id' : fields.many2one('res.users', 'User'),
431         'response_type' : fields.selection([('manually', 'Manually'), ('link', 'Link')], 'Response Type', required=1),
432         'question_ids' : fields.one2many('survey.response.line', 'response_id', 'Response Answer'),
433     }
434
435 survey_response()
436
437 class survey_response_line(osv.osv):
438     _name = 'survey.response.line'
439     _description = 'Survey Response Line'
440     _rec_name = 'date_create'
441     _columns = {
442         'response_id' : fields.many2one('survey.response', 'Response', ondelete='cascade'),
443         'date_create' : fields.datetime('Create Date', required=1),
444         'state' : fields.selection([('draft', 'Draft'), ('done', 'Answered'),('skip', 'Skiped')], 'Status', readonly=True),
445         'question_id' : fields.many2one('survey.question', 'Question', ondelete='cascade'),
446         'page_id' : fields.related('question_id', 'page_id', type='many2one', relation='survey.page', string='Page'),
447         'response_answer_ids' : fields.one2many('survey.response.answer', 'response_id', 'Response Answer'),
448         'comment' : fields.text('Notes'),
449         'single_text' : fields.char('Text', size=255),
450     }
451     _defaults = {
452         'state' : lambda * a: "draft",
453     }
454
455     def response_draft(self, cr, uid, ids, arg):
456         self.write(cr, uid, ids, { 'state' : 'draft' })
457         return True
458
459     def response_done(self, cr, uid, ids, arg):
460         self.write(cr, uid, ids, { 'state' : 'done' })
461         return True
462
463     def response_skip(self, cr, uid, ids, arg):
464         self.write(cr, uid, ids, { 'state' : 'skip' })
465         return True
466
467 survey_response_line()
468
469 class survey_response_answer(osv.osv):
470     _name = 'survey.response.answer'
471     _description = 'Survey Response Answer'
472     _rec_name = 'response_id'
473     _columns = {
474         'response_id' : fields.many2one('survey.response.line', 'Response', ondelete='cascade'),
475         'answer_id' : fields.many2one('survey.answer', 'Answer', required=1, ondelete='cascade'),
476         'answer' : fields.char('Value', size =255),
477         'value_choice' : fields.char('Value Choice', size =255),
478         'comment' : fields.text('Notes'),
479         'comment_field' : fields.char('Comment', size = 255)
480     }
481 survey_response_answer()
482
483
484 class survey_name_wiz(osv.osv_memory):
485     _name = 'survey.name.wiz'
486
487     def _get_survey(self, cr, uid, context=None):
488         surv_obj = self.pool.get("survey")
489         result = []
490         if context.has_key('survey_id'):
491             for sur in surv_obj.browse(cr, uid, [context['survey_id']]):
492                 result.append((sur.id, sur.title))
493             return result
494         group_id = self.pool.get('res.groups').search(cr, uid, [('name', '=', 'Survey / Manager')])
495         user_obj = self.pool.get('res.users')
496         user_rec = user_obj.read(cr, uid, uid)
497         for sur in surv_obj.browse(cr, uid, surv_obj.search(cr, uid, [])):
498             if sur.state == 'open':
499                 if group_id[0]  in user_rec['groups_id']:
500                     result.append((sur.id, sur.title))
501                 elif sur.id in user_rec['survey_id']:
502                     result.append((sur.id, sur.title))
503         return result
504
505     _columns = {
506         'survey_id': fields.selection(_get_survey, "Survey", required="1"),
507         'page_no' : fields.integer('Page Number'),
508         'note' : fields.text("Description"),
509         'page' : fields.char('Page Position',size = 12),
510         'transfer' : fields.boolean('Page Transfer'),
511         'store_ans' : fields.text('Store Answer'),
512         'response' : fields.char('Response',size=16)
513     }
514     _defaults = {
515         'page_no' : lambda * a: - 1,
516         'page' : lambda * a: 'next',
517         'transfer' : lambda * a: 1,
518         'response' : lambda * a: 0,
519     }
520
521     def action_next(self, cr, uid, ids, context=None):
522         sur_id = self.read(cr, uid, ids, [])[0]
523         survey_id = sur_id['survey_id']
524         context.update({'survey_id' : survey_id, 'sur_name_id' : sur_id['id']})
525         cr.execute('select count(id) from survey_history where user_id=%s\
526                     and survey_id=%s' % (uid,survey_id))
527         res = cr.fetchone()[0]
528         user_limit = self.pool.get('survey').read(cr, uid, survey_id, ['response_user'])['response_user']
529         if user_limit and res >= user_limit:
530             raise osv.except_osv(_('Warning !'),_("You can not give response for this survey more than %s times") % (user_limit))
531         his_id = self.pool.get('survey.history').create(cr, uid, {'user_id': uid, \
532                                           'date': strftime('%Y-%m-%d %H:%M:%S'),\
533                                           'survey_id': survey_id})
534         survey_obj = self.pool.get('survey')
535         sur_rec = survey_obj.read(cr,uid,self.read(cr,uid,ids)[0]['survey_id'])
536         survey_obj.write(cr, uid, survey_id, {'tot_start_survey' : sur_rec['tot_start_survey'] + 1})
537
538         search_obj = self.pool.get('ir.ui.view')
539         search_id = search_obj.search(cr,uid,[('model','=','survey.question.wiz'),('name','=','Survey Search')])
540
541         return {
542             'view_type': 'form',
543             "view_mode": 'form',
544             'res_model': 'survey.question.wiz',
545             'type': 'ir.actions.act_window',
546             'target': 'new',
547             'search_view_id':search_id[0],
548             'context' : context
549          }
550
551     def on_change_survey(self, cr, uid, ids, survey_id, context=None):
552         notes = self.pool.get('survey').read(cr, uid, survey_id, ['note'])['note']
553         return {'value': {'note' : notes}}
554
555 survey_name_wiz()
556
557 class survey_question_wiz(osv.osv_memory):
558     _name = 'survey.question.wiz'
559     _columns = {
560         'name': fields.integer('Number'),
561     }
562
563     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False,submenu=False):
564         result = super(survey_question_wiz, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar,submenu)
565         surv_name_wiz = self.pool.get('survey.name.wiz')
566         if view_type in ['form']:
567             sur_name_rec = surv_name_wiz.read(cr, uid, context['sur_name_id'])
568             survey_id = context['survey_id']
569             survey_obj = self.pool.get('survey')
570             sur_rec = survey_obj.read(cr, uid, survey_id, [])
571             page_obj = self.pool.get('survey.page')
572             que_obj = self.pool.get('survey.question')
573             ans_obj = self.pool.get('survey.answer')
574             response_obj = self.pool.get('survey.response.line')
575             que_col_head = self.pool.get('survey.question.column.heading')
576             tbl_col = self.pool.get('survey.tbl.column.heading')
577             p_id = sur_rec['page_ids']
578             total_pages = len(p_id)
579             pre_button = False
580             if not sur_name_rec['page_no'] + 1 :
581                 surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':{}})
582             sur_name_read = surv_name_wiz.read(cr, uid, context['sur_name_id'])
583             page_number = int(sur_name_rec['page_no'])
584             if sur_name_read['transfer'] or not sur_name_rec['page_no'] + 1 :
585                 surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'transfer':False})
586                 flag = False
587                 if sur_name_read['page'] == "next" or sur_name_rec['page_no'] == - 1 :
588                     if len(p_id) > sur_name_rec['page_no'] + 1 :
589                         if sur_rec['max_response_limit'] and sur_rec['max_response_limit'] <= sur_rec['tot_start_survey'] and not sur_name_rec['page_no'] + 1:
590                             survey_obj.write(cr, uid, survey_id, {'state':'close', 'date_close':strftime("%Y-%m-%d %H:%M:%S")})
591                         p_id = p_id[sur_name_rec['page_no'] + 1]
592                         surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'page_no' : sur_name_rec['page_no'] + 1})
593                         flag = True
594                         page_number += 1
595                     if sur_name_rec['page_no'] > - 1:
596                         pre_button = True
597                 else:
598                     if sur_name_rec['page_no'] != 0:
599                         p_id = p_id[sur_name_rec['page_no'] - 1]
600                         surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'page_no' : sur_name_rec['page_no'] - 1})
601                         flag = True
602                         page_number -= 1
603                     if sur_name_rec['page_no'] > 1:
604                         pre_button = True
605                 if flag:
606                     fields = {}
607                     pag_rec = page_obj.read(cr, uid, p_id)
608                     xml_form = etree.Element('form', {'string': _(tools.ustr(pag_rec['title']))})
609                     etree.SubElement(xml_form, 'label', {'string': to_xml(tools.ustr(pag_rec['note'] or '')), 'align': '0.0', 'colspan':'4'})
610                     que_ids = pag_rec['question_ids']
611                     qu_no = 0
612                     for que in que_ids:
613                         qu_no += 1
614                         que_rec = que_obj.read(cr, uid, que)
615                         descriptive_text = ""
616                         separator_string = tools.ustr(qu_no) + "." + tools.ustr(que_rec['question'])
617                         xml_group = etree.SubElement(xml_form, 'group', {'col': '1', 'colspan': '4'})
618                         etree.SubElement(xml_group, 'separator', {'string': to_xml(separator_string), 'colspan': '4'})
619                         ans_ids = ans_obj.read(cr, uid, que_rec['answer_choice_ids'], [])
620                         if que_rec['type'] == 'multiple_choice_only_one_ans':
621                             selection = []
622                             for ans in ans_ids:
623                                 selection.append((tools.ustr(ans['id']), ans['answer']))
624                             xml_group = etree.SubElement(xml_group, 'group', {'col': '2', 'colspan': '2'})
625                             etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_selection"})
626                             fields[tools.ustr(que) + "_selection"] = {'type':'selection', 'selection' :selection, 'string':"Answer"}
627                         elif que_rec['type'] == 'multiple_choice_multiple_ans':
628                             xml_group = etree.SubElement(xml_group, 'group', {'col': '4', 'colspan': '4'})
629                             for ans in ans_ids:
630                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(ans['id'])})
631                                 fields[tools.ustr(que) + "_" + tools.ustr(ans['id'])] = {'type':'boolean', 'string':ans['answer']}
632                         elif que_rec['type'] in ['matrix_of_choices_only_one_ans', 'rating_scale']:
633                             if que_rec['comment_column']:
634                                 col = "4"
635                                 colspan = "4"
636                             else:
637                                col = "2"
638                                colspan = "2"
639                             xml_group = etree.SubElement(xml_group, 'group', {'col': tools.ustr(col), 'colspan': tools.ustr(colspan)})
640                             for row in ans_ids:
641                                 etree.SubElement(xml_group, 'newline')
642                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_selection_" + tools.ustr(row['id']),'string':to_xml(tools.ustr(row['answer']))})
643                                 selection = [('','')]
644                                 for col in que_col_head.read(cr, uid, que_rec['column_heading_ids']):
645                                     selection.append((col['title'], col['title']))
646                                 fields[tools.ustr(que) + "_selection_" + tools.ustr(row['id'])] = {'type':'selection', 'selection' : selection, 'string': "Answer"}
647                                 if que_rec['comment_column']:
648                                    fields[tools.ustr(que) + "_commentcolumn_"+tools.ustr(row['id']) + "_field"] = {'type':'char', 'size' : 255, 'string':tools.ustr(que_rec['column_name']), 'views':{}}
649                                    etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_commentcolumn_"+tools.ustr(row['id'])+ "_field"})
650                         elif que_rec['type'] == 'matrix_of_choices_only_multi_ans':
651                             xml_group = etree.SubElement(xml_group, 'group', {'col': '2', 'colspan': '2'})
652                             for row in ans_ids:
653                                 etree.SubElement(xml_group, 'label', {'string': to_xml(tools.ustr(row['answer'])) +' :-', 'align': '0.0'})
654                                 etree.SubElement(xml_group, 'newline')
655                                 for col in que_col_head.read(cr, uid, que_rec['column_heading_ids']):
656                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(row['id']) + "_" + tools.ustr(col['title'])})
657                                     fields[tools.ustr(que) + "_" + tools.ustr(row['id'])  + "_" + tools.ustr(col['title'])] = {'type':'boolean', 'string': col['title']}
658                         elif que_rec['type'] == 'matrix_of_drop_down_menus':
659                             xml_group = etree.SubElement(xml_group, 'group', {'col': '2', 'colspan': '2'})
660                             for row in ans_ids:
661                                 etree.SubElement(xml_group, 'label', {'string': to_xml(tools.ustr(row['answer']))+' :-', 'align': '0.0'})
662                                 etree.SubElement(xml_group, 'newline')
663                                 for col in que_col_head.read(cr, uid, que_rec['column_heading_ids']):
664                                     selection = []
665                                     if col['menu_choice']:
666                                         for item in col['menu_choice'].split('\n'):
667                                             if item and not item.strip() == '': selection.append((item ,item))
668                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(row['id']) + "_" + tools.ustr(col['title'])})
669                                     fields[tools.ustr(que) + "_" + tools.ustr(row['id'])  + "_" + tools.ustr(col['title'])] = {'type':'selection', 'string': col['title'], 'selection':selection}
670                         elif que_rec['type'] == 'multiple_textboxes':
671                             xml_group = etree.SubElement(xml_group, 'group', {'col': '1', 'colspan': '4'})
672                             for ans in ans_ids:
673                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(ans['id']) + "_multi"})
674                                 fields[tools.ustr(que) + "_" + tools.ustr(ans['id']) + "_multi"] = {'type':'char', 'size':255, 'string':ans['answer']}
675                         elif que_rec['type'] == 'numerical_textboxes':
676                             xml_group = etree.SubElement(xml_group, 'group', {'col': '2', 'colspan': '2'})
677                             for ans in ans_ids:
678                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(ans['id']) + "_numeric"})
679                                 fields[tools.ustr(que) + "_" + tools.ustr(ans['id']) + "_numeric"] = {'type':'integer', 'string':ans['answer']}
680                         elif que_rec['type'] == 'date':
681                             xml_group = etree.SubElement(xml_group, 'group', {'col': '2', 'colspan': '2'})
682                             for ans in ans_ids:
683                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(ans['id'])})
684                                 fields[tools.ustr(que) + "_" + tools.ustr(ans['id'])] = {'type':'date', 'string':ans['answer']}
685                         elif que_rec['type'] == 'date_and_time':
686                             xml_group = etree.SubElement(xml_group, 'group', {'col': '2', 'colspan': '2'})
687                             for ans in ans_ids:
688                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_" + tools.ustr(ans['id'])})
689                                 fields[tools.ustr(que) + "_" + tools.ustr(ans['id'])] = {'type':'datetime', 'string':ans['answer']}
690                         elif que_rec['type'] == 'descriptive_text':
691                             etree.SubElement(xml_group, 'label', {'string': to_xml(tools.ustr(que_rec['descriptive_text']))})
692                         elif que_rec['type'] == 'single_textbox':
693                             etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_single", 'nolabel':"1" ,'colspan':"4"})
694                             fields[tools.ustr(que) + "_single"] = {'type':'char', 'size' : 255, 'string':"single_textbox", 'views':{}}
695                         elif que_rec['type'] == 'comment':
696                             etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_comment", 'nolabel':"1" ,'colspan':"4"})
697                             fields[tools.ustr(que) + "_comment"] = {'type':'text', 'string':"Comment/Eassy Box", 'views':{}}
698                         elif que_rec['type'] == 'table':
699                             xml_group = etree.SubElement(xml_group, 'group', {'col': str(len(que_rec['column_ids'])), 'colspan': '4'})
700                             for col in tbl_col.read(cr, uid, que_rec['column_ids']):
701                                 etree.SubElement(xml_group, 'separator', {'string': tools.ustr(col['name']),'colspan': '1'})
702                             for row in range(0,que_rec['no_of_rows']):
703                                 for col in tbl_col.read(cr, uid, que_rec['column_ids']):
704                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) +"_"+ tools.ustr(row)+ tools.ustr(col['name']), 'nolabel':"1"})
705                                     fields[tools.ustr(que)+"_"+ tools.ustr(row)+tools.ustr(col['name'])] = {'type':'char','size':255,'views':{}}
706                         if que_rec['type'] in ['multiple_choice_only_one_ans', 'multiple_choice_multiple_ans', 'matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'matrix_of_drop_down_menus', 'rating_scale']:
707                             if que_rec['type'] in ['multiple_choice_only_one_ans', 'multiple_choice_multiple_ans'] and que_rec['comment_field_type'] in ['char','text'] and que_rec['make_comment_field']:
708                                 etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_otherfield", 'colspan':"4"})
709                                 fields[tools.ustr(que) + "_otherfield"] = {'type':'boolean', 'string':que_rec['comment_label'], 'views':{}}
710                                 if que_rec['comment_field_type'] == 'char':
711                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_other", 'nolabel':"1" ,'colspan':"4"})
712                                     fields[tools.ustr(que) + "_other"] = {'type': 'char', 'string': '', 'size':255, 'views':{}}
713                                 elif que_rec['comment_field_type'] == 'text':
714                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_other", 'nolabel':"1" ,'colspan':"4"})
715                                     fields[tools.ustr(que) + "_other"] = {'type': 'text', 'string': '', 'views':{}}
716                             else:
717                                 if que_rec['comment_field_type'] == 'char':
718                                     etree.SubElement(xml_group, 'label', {'string': to_xml(tools.ustr(que_rec['comment_label'])),'colspan':"4"})
719                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_other", 'nolabel':"1" ,'colspan':"4"})
720                                     fields[tools.ustr(que) + "_other"] = {'type': 'char', 'string': '', 'size':255, 'views':{}}
721                                 elif que_rec['comment_field_type'] == 'text':
722                                     etree.SubElement(xml_group, 'label', {'string': to_xml(tools.ustr(que_rec['comment_label'])),'colspan':"4"})
723                                     etree.SubElement(xml_group, 'field', {'name': tools.ustr(que) + "_other", 'nolabel':"1" ,'colspan':"4"})
724                                     fields[tools.ustr(que) + "_other"] = {'type': 'text', 'string': '', 'views':{}}
725                     etree.SubElement(xml_form, 'separator', {'colspan': '4'})
726                     xml_group = etree.SubElement(xml_form, 'group', {'col': '6', 'colspan': '4'})
727                     etree.SubElement(xml_group, 'field', {'name': 'progress_bar_' + tools.ustr(page_number) , 'widget':'progressbar'})
728                     fields['progress_bar_' + tools.ustr(page_number)] = {'type':'int', 'string':"Progress", 'views':{}}
729                     etree.SubElement(xml_group, 'label', {'string': tools.ustr(page_number+ 1) + "/" + tools.ustr(total_pages)})
730                     etree.SubElement(xml_group, 'button', {'icon': "gtk-cancel", 'special': "cancel",'string':"Cancel"})
731                     if pre_button:
732                         etree.SubElement(xml_group, 'button', {'colspan':"1",'icon':"gtk-go-back",'name':"action_previous",'string':"Previous",'type':"object"})
733                     etree.SubElement(xml_group, 'button', {'icon': "gtk-go-forward", 'name':"action_next",'string':"Next",'type':"object"})
734                     root = xml_form.getroottree()
735                     root.write('/tmp/arch4.xml', pretty_print=True)
736                     result['arch'] = etree.tostring(root)
737                     result['fields'] = fields
738                     result['context'] = context
739                 else:
740                     if not context.has_key('active'):
741                         survey_obj.write(cr, uid, survey_id, {'tot_comp_survey' : sur_rec['tot_comp_survey'] + 1})
742                     xml_form = etree.Element('form', {'string': _('Complete Survey Response')})
743                     etree.SubElement(xml_form, 'separator', {'string': 'Complete Survey', 'colspan': "4"})
744                     etree.SubElement(xml_form, 'label', {'string': 'Thanks for your response'})
745                     etree.SubElement(xml_form, 'newline')
746                     etree.SubElement(xml_form, 'button', {'icon': "gtk-go-forward", 'special':"cancel",'string':"OK",'colspan':"2"})
747                     root = xml_form.getroottree()
748                     result['arch'] = etree.tostring(root)
749                     result['fields'] = {}
750                     result['context'] = context
751         return result
752
753     def default_get(self, cr, uid, fields_list, context=None):
754         value = {}
755         for field in fields_list:
756             if field.split('_')[0] == 'progress':
757                 tot_page_id = self.pool.get('survey').browse(cr, uid, context['survey_id'])
758                 tot_per = (float(100) * (int(field.split('_')[2]) + 1) / len(tot_page_id.page_ids))
759                 value[field] = tot_per
760         if context.has_key('active') and context['active']:
761             return value
762         surv_name_wiz = self.pool.get('survey.name.wiz')
763         sur_name_read = surv_name_wiz.read(cr, uid, context['sur_name_id'])
764
765         ans_list = []
766         for key,val in sur_name_read['store_ans'].items():
767             for field in fields_list:
768                 if field in list(val):
769                     value[field] = val[field]
770         return value
771
772     def create(self, cr, uid, vals, context=None):
773         if context.has_key('active') and context['active']:
774             return True
775         for key,val in vals.items():
776             if key.split('_')[0] == "progress":
777                 vals.pop(key)
778                 break
779         click_state = True
780         click_update = []
781         surv_name_wiz = self.pool.get('survey.name.wiz')
782         surv_all_resp_obj = self.pool.get('survey.response')
783         sur_name_read = surv_name_wiz.read(cr, uid, context['sur_name_id'])
784         response_id =  0
785         if not sur_name_read['response']:
786             response_id = surv_all_resp_obj.create(cr, uid, {'response_type':'link', 'user_id':uid, 'date_create':datetime.datetime.now(), 'survey_id' : context['survey_id']})
787             surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'response' : tools.ustr(response_id)})
788         else:
789             response_id = int(sur_name_read['response'])
790         for key,val in sur_name_read['store_ans'].items():
791             for field in vals:
792                 if field.split('_')[0] == val['question_id']:
793                     click_state = False
794                     click_update.append(key)
795                     break
796         resp_obj = self.pool.get('survey.response.line')
797         res_ans_obj = self.pool.get('survey.response.answer')
798         que_obj = self.pool.get('survey.question')
799         if click_state:
800             que_li = []
801             resp_id_list = []
802             for key, val in vals.items():
803                 que_id = key.split('_')[0]
804                 if que_id not in que_li:
805                     que_li.append(que_id)
806                     que_rec = que_obj.read(cr, uid, [que_id], [])[0]
807                     resp_id = resp_obj.create(cr, uid, {'question_id':que_id, 'date_create':datetime.datetime.now(), \
808                          'state':'done','response_id' : response_id })
809                     resp_id_list.append(resp_id)
810                     sur_name_read['store_ans'].update({resp_id:{'question_id':que_id}})
811                     surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':sur_name_read['store_ans']})
812                     select_count = 0
813                     numeric_sum = 0
814                     selected_value = []
815                     matrix_list = []
816                     comment_field = False
817                     comment_value = False
818                     response_list = []
819                     for key1, val1 in vals.items():
820                         if val1 and key1.split('_')[1] == "otherfield" and key1.split('_')[0] == que_id:
821                             comment_field = True
822                             sur_name_read['store_ans'][resp_id].update({key1:val1})
823                             select_count += 1
824                             surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':sur_name_read['store_ans']})
825                             continue
826                         elif val1 and key1.split('_')[1] == "selection" and key1.split('_')[0] == que_id:
827                             if len(key1.split('_')) > 2:
828                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':key1.split('_')[-1], 'answer' : val1})
829                                 selected_value.append(val1)
830                                 response_list.append(str(ans_create_id) + "_" + str(key1.split('_')[-1]))
831                             else:
832                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':val1})
833                             sur_name_read['store_ans'][resp_id].update({key1:val1})
834                             select_count += 1
835                         elif key1.split('_')[1] == "other" and key1.split('_')[0] == que_id:
836                             if not val1:
837                                 comment_value = True
838                             else:
839                                 error = False
840                                 if que_rec['comment_valid_type'] == 'must_be_specific_length':
841                                     if (not val1 and  que_rec['validation_minimum_no']) or len(val1) <  que_rec['validation_maximum_no'] or len(val1) > que_rec['comment_maximum_no']:
842                                         error = True
843                                 elif que_rec['comment_valid_type'] in ['must_be_whole_number', 'must_be_decimal_number', 'must_be_date']:
844                                     error = False
845                                     try:
846                                         if que_rec['comment_valid_type'] == 'must_be_whole_number':
847                                             value = int(val1)
848                                             if value <  que_rec['validation_minimum_no'] or value > que_rec['validation_maximum_no']:
849                                                 error = True
850                                         elif que_rec['comment_valid_type'] == 'must_be_decimal_number':
851                                             value = float(val1)
852                                             if value <  que_rec['comment_minimum_float'] or value > que_rec['comment_maximum_float']:
853                                                 error = True
854                                         elif que_rec['comment_valid_type'] == 'must_be_date':
855                                             value = datetime.datetime.strptime(val1, "%Y-%m-%d")
856                                             if value <  datetime.datetime.strptime(que_rec['comment_minimum_date'], "%Y-%m-%d") or value >  datetime.datetime.strptime(que_rec['comment_maximum_date'], "%Y-%m-%d"):
857                                                 error = True
858                                     except:
859                                         error = True
860                                 elif que_rec['comment_valid_type'] == 'must_be_email_address':
861                                     import re
862                                     if re.match("^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", val1) == None:
863                                             error = True
864                                 if error:
865                                     for res in resp_id_list:
866                                         sur_name_read['store_ans'].pop(res)
867                                     raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "'  \n" + tools.ustr(que_rec['comment_valid_err_msg'])))
868                                 resp_obj.write(cr, uid, resp_id, {'comment':val1})
869                                 sur_name_read['store_ans'][resp_id].update({key1:val1})
870                         elif val1 and key1.split('_')[1] == "comment" and key1.split('_')[0] == que_id:
871                             resp_obj.write(cr, uid, resp_id, {'comment':val1})
872                             sur_name_read['store_ans'][resp_id].update({key1:val1})
873                             select_count += 1
874                         elif val1 and key1.split('_')[0] == que_id and (key1.split('_')[1] == "single"  or (len(key1.split('_')) > 2 and key1.split('_')[2] == 'multi')):
875                             error = False
876                             if que_rec['validation_type'] == 'must_be_specific_length':
877                                 if (not val1 and  que_rec['validation_minimum_no']) or len(val1) <  que_rec['validation_minimum_no'] or len(val1) > que_rec['validation_maximum_no']:
878                                     error = True
879                             elif que_rec['validation_type'] in ['must_be_whole_number', 'must_be_decimal_number', 'must_be_date']:
880                                 error = False
881                                 try:
882                                     if que_rec['validation_type'] == 'must_be_whole_number':
883                                         value = int(val1)
884                                         if value <  que_rec['validation_minimum_no'] or value > que_rec['validation_maximum_no']:
885                                             error = True
886                                     elif que_rec['validation_type'] == 'must_be_decimal_number':
887                                         value = float(val1)
888                                         if value <  que_rec['validation_minimum_float'] or value > que_rec['validation_maximum_float']:
889                                             error = True
890                                     elif que_rec['validation_type'] == 'must_be_date':
891                                         value = datetime.datetime.strptime(val1, "%Y-%m-%d")
892                                         if value <  datetime.datetime.strptime(que_rec['validation_minimum_date'], "%Y-%m-%d") or value >  datetime.datetime.strptime(que_rec['validation_maximum_date'], "%Y-%m-%d"):
893                                             error = True
894                                 except:
895                                     error = True
896                             elif que_rec['validation_type'] == 'must_be_email_address':
897                                 import re
898                                 if re.match("^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", val1) == None:
899                                         error = True
900                             if error:
901                                 for res in resp_id_list:
902                                     sur_name_read['store_ans'].pop(res)
903                                 raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "'  \n" + tools.ustr(que_rec['validation_valid_err_msg'])))
904                             if key1.split('_')[1] == "single" :
905                                 resp_obj.write(cr, uid, resp_id, {'single_text':val1})
906                             else:
907                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':key1.split('_')[1], 'answer' : val1})
908                             sur_name_read['store_ans'][resp_id].update({key1:val1})
909                             select_count += 1
910                         elif val1 and que_id == key1.split('_')[0] and len(key1.split('_')) > 2 and key1.split('_')[2] == 'numeric':
911                             ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':key1.split('_')[1], 'answer' : val1})
912                             sur_name_read['store_ans'][resp_id].update({key1:val1})
913                             select_count += 1
914                             numeric_sum += int(val1)
915                         elif val1 and que_id == key1.split('_')[0] and len(key1.split('_')) == 3:
916                             if type(val1) == type('') or type(val1) == type(u''):
917                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':key1.split('_')[1], 'answer' : key1.split('_')[2], 'value_choice' : val1})
918                                 sur_name_read['store_ans'][resp_id].update({key1:val1})
919                             else:
920                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':key1.split('_')[1], 'answer' : key1.split('_')[2]})
921                                 sur_name_read['store_ans'][resp_id].update({key1:True})
922                             matrix_list.append(key1.split('_')[0] + '_' + key1.split('_')[1])
923                             select_count += 1
924                         elif val1 and que_id == key1.split('_')[0] and len(key1.split('_')) == 2:
925                             ans_create_id = res_ans_obj.create(cr, uid, {'response_id':resp_id, 'answer_id':key1.split('_')[-1], 'answer' : val1})
926                             sur_name_read['store_ans'][resp_id].update({key1:val1})
927                             select_count += 1
928                         surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':sur_name_read['store_ans']})
929                     for key,val in vals.items():
930                         if val and key.split('_')[1] == "commentcolumn" and key.split('_')[0] == que_id:
931                             for res_id in response_list:
932                                 if key.split('_')[2] in res_id.split('_')[1]:
933                                     a = res_ans_obj.write(cr, uid, [res_id.split('_')[0]], {'comment_field':val})
934                                     sur_name_read['store_ans'][resp_id].update({key:val})
935                     if comment_field and comment_value:
936                         for res in resp_id_list:
937                             sur_name_read['store_ans'].pop(res)
938                         raise osv.except_osv(_('Error re !'), _("'" + que_rec['question']  + "' " + tools.ustr(que_rec['make_comment_field_err_msg'])))
939                     if que_rec['type'] == "rating_scale" and que_rec['rating_allow_one_column_require'] and len(selected_value) > len(list(set(selected_value))):
940                         for res in resp_id_list:
941                             sur_name_read['store_ans'].pop(res)
942                         raise osv.except_osv(_('Error re !'), _("'" + que_rec['question'] + "\n you cannot select same answer more than one times'"))
943                     if not select_count:
944                         resp_obj.write(cr, uid, resp_id, {'state':'skip'})
945                     if que_rec['numeric_required_sum'] and numeric_sum > que_rec['numeric_required_sum']:
946                         for res in resp_id_list:
947                             sur_name_read['store_ans'].pop(res)
948                         raise osv.except_osv(_('Error re !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['numeric_required_sum_err_msg'])))
949                     if que_rec['type'] in ['multiple_choice_multiple_ans','matrix_of_choices_only_one_ans','matrix_of_choices_only_multi_ans','matrix_of_drop_down_menus','rating_scale','multiple_textboxes','numerical_textboxes','date','date_and_time'] and que_rec['required_type']:
950                         if matrix_list:
951                             if (que_rec['required_type'] == 'all' and len(list(set(matrix_list))) < len(que_rec['answer_choice_ids'])) or \
952                             (que_rec['required_type'] == 'at least' and len(list(set(matrix_list))) < que_rec['req_ans']) or \
953                             (que_rec['required_type'] == 'at most' and len(list(set(matrix_list))) > que_rec['req_ans']) or \
954                             (que_rec['required_type'] == 'exactly' and len(list(set(matrix_list))) != que_rec['req_ans']) or \
955                             (que_rec['required_type'] == 'a range' and (len(list(set(matrix_list))) < que_rec['minimum_req_ans'] or len(list(set(matrix_list))) > que_rec['maximum_req_ans'])):
956                                 for res in resp_id_list:
957                                     sur_name_read['store_ans'].pop(res)
958                                 raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['req_error_msg'])))
959                         elif (que_rec['required_type'] == 'all' and select_count < len(que_rec['answer_choice_ids'])) or \
960                             (que_rec['required_type'] == 'at least' and select_count < que_rec['req_ans']) or \
961                             (que_rec['required_type'] == 'at most' and select_count > que_rec['req_ans']) or \
962                             (que_rec['required_type'] == 'exactly' and select_count != que_rec['req_ans']) or \
963                             (que_rec['required_type'] == 'a range' and (select_count < que_rec['minimum_req_ans'] or select_count > que_rec['maximum_req_ans'])):
964                             for res in resp_id_list:
965                                 sur_name_read['store_ans'].pop(res)
966                             raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['req_error_msg'])))
967                     if que_rec['type'] in ['multiple_choice_only_one_ans','single_textbox','comment'] and  que_rec['is_require_answer'] and select_count <= 0:
968                         for res in resp_id_list:
969                             sur_name_read['store_ans'].pop(res)
970                         raise osv.except_osv(_('Error re !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['req_error_msg'])))
971
972         else:
973             resp_id_list = []
974             for update in click_update:
975                 que_rec = que_obj.read(cr, uid , [sur_name_read['store_ans'][update]['question_id']], [])[0]
976                 res_ans_obj.unlink(cr, uid,res_ans_obj.search(cr, uid, [('response_id', '=', update)]))
977                 resp_id_list.append(update)
978                 sur_name_read['store_ans'].update({update:{'question_id':sur_name_read['store_ans'][update]['question_id']}})
979                 surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':sur_name_read['store_ans']})
980                 select_count = 0
981                 numeric_sum = 0
982                 selected_value = []
983                 matrix_list = []
984                 comment_field = False
985                 comment_value = False
986                 response_list = []
987                 for key, val in vals.items():
988                     ans_id_len = key.split('_')
989                     if ans_id_len[0] == sur_name_read['store_ans'][update]['question_id']:
990                         if val and key.split('_')[1] == "otherfield" :
991                             comment_field = True
992                             sur_name_read['store_ans'][update].update({key:val})
993                             select_count += 1
994                             surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':sur_name_read['store_ans']})
995                             continue
996                         elif val and key.split('_')[1] == "selection":
997                             if len(key.split('_')) > 2:
998                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id':key.split('_')[-1], 'answer' : val})
999                                 selected_value.append(val)
1000                                 response_list.append(str(ans_create_id) + "_" + str(key.split('_')[-1]))
1001                             else:
1002                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id': val})
1003                             resp_obj.write(cr, uid, update, {'state': 'done'})
1004                             sur_name_read['store_ans'][update].update({key:val})
1005                             select_count += 1
1006                         elif key.split('_')[1] == "other":
1007                             if not val:
1008                                 comment_value = True
1009                             else:
1010                                 error = False
1011                                 if que_rec['comment_valid_type'] == 'must_be_specific_length':
1012                                     if (not val and  que_rec['comment_minimum_no']) or len(val) <  que_rec['comment_minimum_no'] or len(val) > que_rec['comment_maximum_no']:
1013                                         error = True
1014                                 elif que_rec['comment_valid_type'] in ['must_be_whole_number', 'must_be_decimal_number', 'must_be_date']:
1015                                     try:
1016                                         if que_rec['comment_valid_type'] == 'must_be_whole_number':
1017                                             value = int(val)
1018                                             if value <  que_rec['comment_minimum_no'] or value > que_rec['comment_maximum_no']:
1019                                                 error = True
1020                                         elif que_rec['comment_valid_type'] == 'must_be_decimal_number':
1021                                             value = float(val)
1022                                             if value <  que_rec['comment_minimum_float'] or value > que_rec['comment_maximum_float']:
1023                                                 error = True
1024                                         elif que_rec['comment_valid_type'] == 'must_be_date':
1025                                             value = datetime.datetime.strptime(val, "%Y-%m-%d")
1026                                             if value <  datetime.datetime.strptime(que_rec['comment_minimum_date'], "%Y-%m-%d") or value >  datetime.datetime.strptime(que_rec['comment_maximum_date'], "%Y-%m-%d"):
1027                                                 error = True
1028                                     except:
1029                                         error = True
1030                                 elif que_rec['comment_valid_type'] == 'must_be_email_address':
1031                                     import re
1032                                     if re.match("^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", val) == None:
1033                                             error = True
1034                                 if error:
1035                                     raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "'  \n" + tools.ustr(que_rec['comment_valid_err_msg'])))
1036
1037                                 resp_obj.write(cr, uid, update, {'comment':val,'state': 'done'})
1038                                 sur_name_read['store_ans'][update].update({key:val})
1039                         elif val and key.split('_')[1] == "comment":
1040                             resp_obj.write(cr, uid, update, {'comment':val,'state': 'done'})
1041                             sur_name_read['store_ans'][update].update({key:val})
1042                             select_count += 1
1043                         elif val and (key.split('_')[1] == "single"  or (len(key.split('_')) > 2 and key.split('_')[2] == 'multi')):
1044                             error = False
1045                             if que_rec['validation_type'] == 'must_be_specific_length':
1046                                 if (not val and  que_rec['validation_minimum_no']) or len(val) <  que_rec['validation_minimum_no'] or len(val) > que_rec['validation_maximum_no']:
1047                                     error = True
1048                             elif que_rec['validation_type'] in ['must_be_whole_number', 'must_be_decimal_number', 'must_be_date']:
1049                                 error = False
1050                                 try:
1051                                     if que_rec['validation_type'] == 'must_be_whole_number':
1052                                         value = int(val)
1053                                         if value <  que_rec['validation_minimum_no'] or value > que_rec['validation_maximum_no']:
1054                                             error = True
1055                                     elif que_rec['validation_type'] == 'must_be_decimal_number':
1056                                         value = float(val)
1057                                         if value <  que_rec['validation_minimum_float'] or value > que_rec['validation_maximum_float']:
1058                                             error = True
1059                                     elif que_rec['validation_type'] == 'must_be_date':
1060                                         value = datetime.datetime.strptime(val, "%Y-%m-%d")
1061                                         if value <  datetime.datetime.strptime(que_rec['validation_minimum_date'], "%Y-%m-%d") or value >  datetime.datetime.strptime(que_rec['validation_maximum_date'], "%Y-%m-%d"):
1062                                             error = True
1063                                 except Exception ,e:
1064                                     error = True
1065                             elif que_rec['validation_type'] == 'must_be_email_address':
1066                                 import re
1067                                 if re.match("^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", val) == None:
1068                                         error = True
1069                             if error:
1070                                 raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "'  \n" + tools.ustr(que_rec['validation_valid_err_msg'])))
1071                             if key.split('_')[1] == "single" :
1072                                 resp_obj.write(cr, uid, update, {'single_text':val,'state': 'done'})
1073                             else:
1074                                 resp_obj.write(cr, uid, update, {'state': 'done'})
1075                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id':ans_id_len[1], 'answer' : val})
1076                             sur_name_read['store_ans'][update].update({key:val})
1077                             select_count += 1
1078                         elif val and len(key.split('_')) > 2 and key.split('_')[2] == 'numeric':
1079                             resp_obj.write(cr, uid, update, {'state': 'done'})
1080                             ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id':ans_id_len[1], 'answer' : val})
1081                             sur_name_read['store_ans'][update].update({key:val})
1082                             select_count += 1
1083                             numeric_sum += int(val)
1084                         elif val and len(key.split('_')) == 3:
1085                             resp_obj.write(cr, uid, update, {'state': 'done'})
1086                             if type(val) == type(''):
1087                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id':ans_id_len[1], 'answer' : ans_id_len[2], 'value_choice' : val})
1088                                 sur_name_read['store_ans'][update].update({key:val})
1089                             else:
1090                                 ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id':ans_id_len[1], 'answer' : ans_id_len[2]})
1091                                 sur_name_read['store_ans'][update].update({key:True})
1092                             matrix_list.append(key.split('_')[0] + '_' + key.split('_')[1])
1093                             select_count += 1
1094                         elif val and len(key.split('_')) == 2:
1095                             resp_obj.write(cr, uid, update, {'state': 'done'})
1096                             ans_create_id = res_ans_obj.create(cr, uid, {'response_id':update, 'answer_id':ans_id_len[-1], 'answer' : val})
1097                             sur_name_read['store_ans'][update].update({key:val})
1098                             select_count += 1
1099                         surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'store_ans':sur_name_read['store_ans']})
1100                 for key,val in vals.items():
1101                     if val and key.split('_')[1] == "commentcolumn" and key.split('_')[0] == sur_name_read['store_ans'][update]['question_id']:
1102                         for res_id in response_list:
1103                             if key.split('_')[2] in res_id.split('_')[1]:
1104                                 a = res_ans_obj.write(cr, uid, [res_id.split('_')[0]], {'comment_field':val})
1105                                 sur_name_read['store_ans'][update].update({key:val})
1106
1107                 if comment_field and comment_value:
1108                     raise osv.except_osv(_('Error re !'), _("'" + que_rec['question']  + "' " + tools.ustr(que_rec['make_comment_field_err_msg'])))
1109                 if que_rec['type'] == "rating_scale" and que_rec['rating_allow_one_column_require'] and len(selected_value) > len(list(set(selected_value))):
1110                     raise osv.except_osv(_('Error re !'), _("'" + que_rec['question'] + "\n you cannot select same answer more than one times'"))
1111                 if que_rec['numeric_required_sum'] and numeric_sum > que_rec['numeric_required_sum']:
1112                     raise osv.except_osv(_('Error re !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['numeric_required_sum_err_msg'])))
1113                 if not select_count:
1114                     resp_obj.write(cr, uid, update, {'state': 'skip'})
1115                 if que_rec['type'] in ['multiple_choice_multiple_ans','matrix_of_choices_only_one_ans','matrix_of_choices_only_multi_ans','matrix_of_drop_down_menus','rating_scale','multiple_textboxes','numerical_textboxes','date','date_and_time'] and que_rec['required_type']:
1116                     if matrix_list:
1117                         if (que_rec['required_type'] == 'all' and len(list(set(matrix_list))) < len(que_rec['answer_choice_ids'])) or \
1118                         (que_rec['required_type'] == 'at least' and len(list(set(matrix_list))) < que_rec['req_ans']) or \
1119                         (que_rec['required_type'] == 'at most' and len(list(set(matrix_list))) > que_rec['req_ans']) or \
1120                         (que_rec['required_type'] == 'exactly' and len(list(set(matrix_list))) != que_rec['req_ans']) or \
1121                         (que_rec['required_type'] == 'a range' and (len(list(set(matrix_list))) < que_rec['minimum_req_ans'] or len(list(set(matrix_list))) > que_rec['maximum_req_ans'])):
1122                             raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['req_error_msg'])))
1123                     elif (que_rec['required_type'] == 'all' and select_count < len(que_rec['answer_choice_ids'])) or \
1124                         (que_rec['required_type'] == 'at least' and select_count < que_rec['req_ans']) or \
1125                         (que_rec['required_type'] == 'at most' and select_count > que_rec['req_ans']) or \
1126                         (que_rec['required_type'] == 'exactly' and select_count != que_rec['req_ans']) or \
1127                         (que_rec['required_type'] == 'a range' and (select_count < que_rec['minimum_req_ans'] or select_count > que_rec['maximum_req_ans'])):
1128                             raise osv.except_osv(_('Error !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['req_error_msg'])))
1129                 if que_rec['type'] in ['multiple_choice_only_one_ans','single_textbox','comment'] and  que_rec['is_require_answer'] and select_count <= 0:
1130                     raise osv.except_osv(_('Error re !'), _("'" + que_rec['question'] + "' " + tools.ustr(que_rec['req_error_msg'])))
1131         return True
1132
1133     def action_next(self, cr, uid, ids, context=None):
1134         surv_name_wiz = self.pool.get('survey.name.wiz')
1135         search_obj = self.pool.get('ir.ui.view')
1136         search_id = search_obj.search(cr,uid,[('model','=','survey.question.wiz'),('name','=','Survey Search')])
1137         surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'transfer':True, 'page':'next'})
1138         return {
1139                 'view_type': 'form',
1140                 "view_mode": 'form',
1141                 'res_model': 'survey.question.wiz',
1142                 'type': 'ir.actions.act_window',
1143                 'target': 'new',
1144                 'search_view_id':search_id[0],
1145                 'context': context
1146                 }
1147
1148     def action_previous(self, cr, uid, ids, context=None):
1149         surv_name_wiz = self.pool.get('survey.name.wiz')
1150         search_obj = self.pool.get('ir.ui.view')
1151         search_id = search_obj.search(cr,uid,[('model','=','survey.question.wiz'),('name','=','Survey Search')])
1152         surv_name_wiz.write(cr, uid, [context['sur_name_id']], {'transfer':True, 'page':'previous'})
1153         return {
1154                 'view_type': 'form',
1155                 "view_mode": 'form',
1156                 'res_model': 'survey.question.wiz',
1157                 'type': 'ir.actions.act_window',
1158                 'target': 'new',
1159                 'search_view_id':search_id[0],
1160                 'context': context
1161                 }
1162 survey_question_wiz()
1163
1164 class res_users(osv.osv):
1165     _inherit = "res.users"
1166     _name = "res.users"
1167     _columns = {
1168         'survey_id': fields.many2many('survey', 'survey_users_rel', 'uid', 'sid', 'Groups'),
1169     }
1170 res_users()
1171
1172 class survey_request(osv.osv):
1173     _name = "survey.request"
1174     _order = 'date_deadline'
1175     _columns = {
1176         'date_deadline' : fields.date("Deadline date"),
1177         'user_id' : fields.many2one("res.users", "User"),
1178         'email' : fields.char("E-mail", size=64),
1179         'survey_id' : fields.many2one("survey", "Survey", required=1),
1180         'answer_ids' : fields.one2many('survey.answer', 'question_id', 'Answer'),
1181         'state' : fields.selection([('waitin_answer', 'Wating Answer'),('done', 'Done'),('cancelled', 'Cancelled')], 'State')
1182     }
1183 survey_request()
1184
1185 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: