app/soc/views/helper/surveys.py
changeset 2451 c58a7ea6c126
parent 2442 dd1f94c3594c
child 2463 05af53239799
equal deleted inserted replaced
2450:596fad6da0e1 2451:c58a7ea6c126
    64       read_only: controls whether the survey taking UI allows data entry.
    64       read_only: controls whether the survey taking UI allows data entry.
    65       editing: controls whether to show the edit or show form.
    65       editing: controls whether to show the edit or show form.
    66     """
    66     """
    67 
    67 
    68     self.kwargs = kwargs
    68     self.kwargs = kwargs
    69     self.survey_content = self.kwargs.get('survey_content', None)
    69     self.survey_content = self.kwargs.pop('survey_content', None)
    70     self.this_user = self.kwargs.get('this_user', None)
    70     self.this_user = self.kwargs.pop('this_user', None)
    71     self.project = self.kwargs.get('project', None)
    71     self.project = self.kwargs.pop('project', None)
    72     self.survey_record = self.kwargs.get('survey_record', None)
    72     self.survey_record = self.kwargs.pop('survey_record', None)
    73 
    73 
    74     del self.kwargs['survey_content']
    74     self.read_only = self.kwargs.pop('read_only', None)
    75     del self.kwargs['this_user']
    75     self.editing = self.kwargs.pop('editing', None)
    76     del self.kwargs['project']
    76 
    77     del self.kwargs['survey_record']
    77     self.fields_map = dict(
    78 
    78         long_answer=self.addLongField,
    79     self.read_only = self.kwargs.get('read_only', None)
    79         short_answer=self.addShortField,
    80     if 'read_only' in self.kwargs:
    80         selection=self.addSingleField,
    81       del self.kwargs['read_only']
    81         pick_multi=self.addMultiField,
    82 
    82         pick_quant=self.addQuantField,
    83     self.editing = self.kwargs.get('editing', None)
    83         )
    84     if 'editing' in self.kwargs:
       
    85       del self.kwargs['editing']
       
    86 
    84 
    87     super(SurveyForm, self).__init__(*args, **self.kwargs)
    85     super(SurveyForm, self).__init__(*args, **self.kwargs)
    88 
    86 
    89   def getFields(self):
    87   def getFields(self):
    90     """Build the SurveyContent (questions) form fields.
    88     """Build the SurveyContent (questions) form fields.
   123           comment = getattr(self.survey_record, 'comment_for_' + field)
   121           comment = getattr(self.survey_record, 'comment_for_' + field)
   124       else:
   122       else:
   125         # use prompts set by survey creator
   123         # use prompts set by survey creator
   126         value = getattr(self.survey_content, field)
   124         value = getattr(self.survey_content, field)
   127 
   125 
   128       if field not in schema:
   126       label = schema.getLabel(field)
   129         logging.error('field %s not found in schema %s' %
   127       if label is None:
   130         (field, str(schema) ) )
   128         continue
   131         continue 
       
   132       elif 'question' in schema[field]:
       
   133         label = schema[field].get('question', None) or field
       
   134       else:
       
   135         label = field
       
   136 
   129 
   137       # dispatch to field-specific methods
   130       # dispatch to field-specific methods
   138       if schema[field]["type"] == "long_answer":
   131       addField = self.fields_map[schema.getType(field)]
   139         self.addLongField(field, value, extra_attrs, label=label,
   132       addField(field, value, extra_attrs, schema, label=label, comment=comment)
   140                           comment=comment)
       
   141       elif schema[field]["type"] == "short_answer":
       
   142         self.addShortField(field, value, extra_attrs, label=label,
       
   143                            comment=comment)
       
   144       elif schema[field]["type"] == "selection":
       
   145         self.addSingleField(field, value, extra_attrs, schema, label=label,
       
   146                             comment=comment)
       
   147       elif schema[field]["type"] == "pick_multi":
       
   148         self.addMultiField(field, value, extra_attrs, schema, label=label,
       
   149                            comment=comment)
       
   150       elif schema[field]["type"] == "pick_quant":
       
   151         self.addQuantField(field, value, extra_attrs, schema, label=label,
       
   152                            comment=comment)
       
   153 
   133 
   154     return self.insertFields()
   134     return self.insertFields()
   155 
   135 
   156   def insertFields(self):
   136   def insertFields(self):
   157     """Add ordered fields to self.fields.
   137     """Add ordered fields to self.fields.
   167         property = 'comment_for_' + property
   147         property = 'comment_for_' + property
   168         self.fields.insert(position - 1, property,
   148         self.fields.insert(position - 1, property,
   169                            self.survey_fields[property])
   149                            self.survey_fields[property])
   170     return self.fields
   150     return self.fields
   171 
   151 
   172   def addLongField(self, field, value, attrs, req=False, label='', tip='',
   152   def addLongField(self, field, value, attrs, schema, req=False, label='', tip='',
   173                    comment=''):
   153                    comment=''):
   174     """Add a long answer fields to this form.
   154     """Add a long answer fields to this form.
   175 
       
   176     params:
       
   177       field: the current field
       
   178       value: the initial value for this field
       
   179       attrs: additional attributes for field
       
   180       req: required bool
       
   181       label: label for field
       
   182       tip: tooltip text for field
       
   183       comment: initial comment value for field 
       
   184     """
       
   185 
       
   186     widget = widgets.Textarea(attrs=attrs)
       
   187 
       
   188     if not tip:
       
   189       tip = 'Please provide a long answer to this question.'
       
   190 
       
   191     question = CharField(help_text=tip, required=req, label=label,
       
   192                          widget=widget, initial=value)
       
   193     self.survey_fields[field] = question
       
   194 
       
   195     if not self.editing:
       
   196       widget = widgets.Textarea(attrs=attrs)
       
   197       comment = CharField(help_text=tip, required=False, label='Comments',
       
   198                           widget=widget, initial=comment)
       
   199       self.survey_fields['comment_for_' + field] = comment
       
   200 
       
   201   def addShortField(self, field, value, attrs, req=False, label='', tip='',
       
   202                     comment=''):
       
   203     """Add a short answer fields to this form.
       
   204 
       
   205     params:
       
   206       field: the current field
       
   207       value: the initial value for this field
       
   208       attrs: additional attributes for field
       
   209       req: required bool
       
   210       label: label for field
       
   211       tip: tooltip text for field
       
   212       comment: initial comment value for field 
       
   213     """
       
   214 
       
   215     attrs['class'] = "text_question"
       
   216     widget = widgets.TextInput(attrs=attrs)
       
   217 
       
   218     if not tip:
       
   219       tip = 'Please provide a short answer to this question.'
       
   220 
       
   221     question = CharField(help_text=tip, required=req, label=label,
       
   222                          widget=widget, max_length=140, initial=value)
       
   223     self.survey_fields[field] = question
       
   224 
       
   225     if not self.editing:
       
   226       widget = widgets.Textarea(attrs=attrs)
       
   227       comment = CharField(help_text=tip, required=False, label='Comments',
       
   228                           widget=widget, initial=comment)
       
   229       self.survey_fields['comment_for_' + field] = comment
       
   230 
       
   231   def addSingleField(self, field, value, attrs, schema, req=False, label='',
       
   232                      tip='', comment=''):
       
   233     """Add a selection field to this form.
       
   234 
   155 
   235     params:
   156     params:
   236       field: the current field
   157       field: the current field
   237       value: the initial value for this field
   158       value: the initial value for this field
   238       attrs: additional attributes for field
   159       attrs: additional attributes for field
   241       label: label for field
   162       label: label for field
   242       tip: tooltip text for field
   163       tip: tooltip text for field
   243       comment: initial comment value for field
   164       comment: initial comment value for field
   244     """
   165     """
   245 
   166 
   246     if self.editing:
   167     widget = widgets.Textarea(attrs=attrs)
   247       kind = schema[field]["type"]
   168 
   248       render = schema[field]["render"]
       
   249       widget = UniversalChoiceEditor(kind, render)
       
   250     else:
       
   251       widget = WIDGETS[schema[field]['render']](attrs=attrs)
       
   252 
       
   253     these_choices = []
       
   254     # add all properties, but select chosen one
       
   255     options = getattr(self.survey_content, field)
       
   256     has_record = not self.editing and self.survey_record
       
   257     if has_record and hasattr(self.survey_record, field):
       
   258       these_choices.append((value, value))
       
   259       if value in options:
       
   260         options.remove(value)
       
   261 
       
   262     for option in options:
       
   263       these_choices.append((option, option))
       
   264     if not tip:
   169     if not tip:
   265       tip = 'Please select an answer this question.'
   170       tip = 'Please provide a long answer to this question.'
   266 
   171 
   267     question = PickOneField(help_text=tip, required=req, label=label,
   172     question = CharField(help_text=tip, required=req, label=label,
   268                             choices=tuple(these_choices), widget=widget)
   173                          widget=widget, initial=value)
       
   174 
   269     self.survey_fields[field] = question
   175     self.survey_fields[field] = question
   270 
   176     self.addCommentField(field, comment, attrs, tip)
   271     if not self.editing:
   177 
   272       widget = widgets.Textarea(attrs=attrs)
   178   def addShortField(self, field, value, attrs, schema, req=False, label='', tip='',
   273       comment = CharField(help_text=tip, required=False, label='Comments',
   179                     comment=''):
   274                           widget=widget, initial=comment)
   180     """Add a short answer fields to this form.
   275       self.survey_fields['comment_for_' + field] = comment
       
   276 
       
   277   def addMultiField(self, field, value, attrs, schema, req=False, label='',
       
   278                     tip='', comment=''):
       
   279     """Add a pick_multi field to this form.
       
   280 
   181 
   281     params:
   182     params:
   282       field: the current field
   183       field: the current field
   283       value: the initial value for this field
   184       value: the initial value for this field
   284       attrs: additional attributes for field
   185       attrs: additional attributes for field
   287       label: label for field
   188       label: label for field
   288       tip: tooltip text for field
   189       tip: tooltip text for field
   289       comment: initial comment value for field
   190       comment: initial comment value for field
   290     """
   191     """
   291 
   192 
   292     if self.editing:
   193     attrs['class'] = "text_question"
   293       kind = schema[field]["type"]
   194     widget = widgets.TextInput(attrs=attrs)
   294       render = schema[field]["render"]
   195 
   295       widget = UniversalChoiceEditor(kind, render)
       
   296     else:
       
   297       widget = WIDGETS[schema[field]['render']](attrs=attrs)
       
   298 
       
   299     # TODO(ajaksu) need to allow checking checkboxes by default
       
   300     if self.survey_record and isinstance(value, basestring):
       
   301       # pass value as 'initial' so MultipleChoiceField renders checked boxes
       
   302       value = value.split(',')
       
   303     else:
       
   304       value = None
       
   305 
       
   306     these_choices = [(v,v) for v in getattr(self.survey_content, field)]
       
   307     if not tip:
   196     if not tip:
   308       tip = 'Please select one or more of these choices.'
   197       tip = 'Please provide a short answer to this question.'
   309 
   198 
   310     question = PickManyField(help_text=tip, required=req, label=label,
   199     question = CharField(help_text=tip, required=req, label=label,
   311                              choices=tuple(these_choices), widget=widget,
   200                          widget=widget, max_length=140, initial=value)
   312                              initial=value)
   201 
   313     self.survey_fields[field] = question
   202     self.survey_fields[field] = question
   314     if not self.editing:
   203     self.addCommentField(field, comment, attrs, tip)
   315       widget = widgets.Textarea(attrs=attrs)
   204 
   316       comment = CharField(help_text=tip, required=False, label='Comments',
   205   def addSingleField(self, field, value, attrs, schema, req=False, label='',
   317                           widget=widget, initial=comment)
   206                      tip='', comment=''):
   318       self.survey_fields['comment_for_' + field] = comment
   207     """Add a selection field to this form.
   319 
       
   320   def addQuantField(self, field, value, attrs, schema, req=False, label='',
       
   321                     tip='', comment=''):
       
   322     """Add a pick_quant field to this form.
       
   323 
   208 
   324     params:
   209     params:
   325       field: the current field
   210       field: the current field
   326       value: the initial value for this field
   211       value: the initial value for this field
   327       attrs: additional attributes for field
   212       attrs: additional attributes for field
   330       label: label for field
   215       label: label for field
   331       tip: tooltip text for field
   216       tip: tooltip text for field
   332       comment: initial comment value for field
   217       comment: initial comment value for field
   333     """
   218     """
   334 
   219 
   335     if self.editing:
   220     widget = schema.getWidget(field, self.editing, attrs)
   336       kind = schema[field]["type"]
   221 
   337       render = schema[field]["render"]
   222     these_choices = []
   338       widget = UniversalChoiceEditor(kind, render)
   223     # add all properties, but select chosen one
       
   224     options = getattr(self.survey_content, field)
       
   225     has_record = not self.editing and self.survey_record
       
   226     if has_record and hasattr(self.survey_record, field):
       
   227       these_choices.append((value, value))
       
   228       if value in options:
       
   229         options.remove(value)
       
   230 
       
   231     for option in options:
       
   232       these_choices.append((option, option))
       
   233     if not tip:
       
   234       tip = 'Please select an answer this question.'
       
   235 
       
   236     question = PickOneField(help_text=tip, required=req, label=label,
       
   237                             choices=tuple(these_choices), widget=widget)
       
   238 
       
   239     self.survey_fields[field] = question
       
   240     self.addCommentField(field, comment, attrs, tip)
       
   241 
       
   242   def addMultiField(self, field, value, attrs, schema, req=False, label='',
       
   243                     tip='', comment=''):
       
   244     """Add a pick_multi field to this form.
       
   245 
       
   246     params:
       
   247       field: the current field
       
   248       value: the initial value for this field
       
   249       attrs: additional attributes for field
       
   250       schema: schema for survey
       
   251       req: required bool
       
   252       label: label for field
       
   253       tip: tooltip text for field
       
   254       comment: initial comment value for field
       
   255 
       
   256     """
       
   257 
       
   258     widget = schema.getWidget(field, self.editing, attrs)
       
   259 
       
   260     # TODO(ajaksu) need to allow checking checkboxes by default
       
   261     if self.survey_record and isinstance(value, basestring):
       
   262       # pass value as 'initial' so MultipleChoiceField renders checked boxes
       
   263       value = value.split(',')
   339     else:
   264     else:
   340       widget = WIDGETS[schema[field]['render']](attrs=attrs)
   265       value = None
       
   266 
       
   267     these_choices = [(v,v) for v in getattr(self.survey_content, field)]
       
   268     if not tip:
       
   269       tip = 'Please select one or more of these choices.'
       
   270 
       
   271     question = PickManyField(help_text=tip, required=req, label=label,
       
   272                              choices=tuple(these_choices), widget=widget,
       
   273                              initial=value)
       
   274 
       
   275     self.survey_fields[field] = question
       
   276     self.addCommentField(field, comment, attrs, tip)
       
   277 
       
   278   def addQuantField(self, field, value, attrs, schema, req=False, label='',
       
   279                     tip='', comment=''):
       
   280     """Add a pick_quant field to this form.
       
   281 
       
   282     params:
       
   283       field: the current field
       
   284       value: the initial value for this field
       
   285       attrs: additional attributes for field
       
   286       schema: schema for survey
       
   287       req: required bool
       
   288       label: label for field
       
   289       tip: tooltip text for field
       
   290       comment: initial comment value for field
       
   291 
       
   292     """
       
   293 
       
   294     widget = schema.getWidget(field, self.editing, attrs)
   341 
   295 
   342     if self.survey_record:
   296     if self.survey_record:
   343       value = value
   297       value = value
   344     else:
   298     else:
   345       value = None
   299       value = None
   350 
   304 
   351     question = PickQuantField(help_text=tip, required=req, label=label,
   305     question = PickQuantField(help_text=tip, required=req, label=label,
   352                              choices=tuple(these_choices), widget=widget,
   306                              choices=tuple(these_choices), widget=widget,
   353                              initial=value)
   307                              initial=value)
   354     self.survey_fields[field] = question
   308     self.survey_fields[field] = question
       
   309     self.addCommentField(field, comment, attrs, tip)
       
   310 
       
   311   def addCommentField(self, field, comment, attrs, tip):
   355     if not self.editing:
   312     if not self.editing:
   356       widget = widgets.Textarea(attrs=attrs)
   313       widget = widgets.Textarea(attrs=attrs)
   357       comment = CharField(help_text=tip, required=False, label='Comments',
   314       comment = CharField(help_text=tip, required=False, label='Comments',
   358                           widget=widget, initial=comment)
   315                           widget=widget, initial=comment)
   359       self.survey_fields['comment_for_' + field] = comment
   316       self.survey_fields['comment_for_' + field] = comment
   360 
   317 
   361 
       
   362   class Meta(object):
   318   class Meta(object):
   363     model = SurveyContent
   319     model = SurveyContent
   364     exclude = ['schema']
   320     exclude = ['schema']
       
   321 
       
   322 
       
   323 class SurveyContentSchema(object):
       
   324   """Abstract question metadata handling.
       
   325   """
       
   326 
       
   327   def __init__(self, schema):
       
   328     self.schema = eval(schema)
       
   329 
       
   330   def getType(self, field):
       
   331     return self.schema[field]["type"]
       
   332 
       
   333   def getRender(self, field):
       
   334     return self.schema[field]["render"]
       
   335 
       
   336   def getWidget(self, field, editing, attrs):
       
   337     """Get survey editing or taking widget for choice questions.
       
   338     """
       
   339 
       
   340     if editing:
       
   341       kind = self.getType(field)
       
   342       render = self.getRender(field)
       
   343       widget = UniversalChoiceEditor(kind, render)
       
   344     else:
       
   345       widget = WIDGETS[self.schema[field]['render']](attrs=attrs)
       
   346     return widget
       
   347 
       
   348   def getLabel(self, field):
       
   349     """Fetch the free text 'question' or use field name as label.
       
   350     """
       
   351 
       
   352     if field not in self.schema:
       
   353       logging.error('field %s not found in schema %s' %
       
   354                     (field, str(self.schema)))
       
   355       return
       
   356     elif 'question' in self.schema[field]:
       
   357       label = self.schema[field].get('question') or field
       
   358     else:
       
   359       label = field
       
   360     return label
   365 
   361 
   366 
   362 
   367 class UniversalChoiceEditor(widgets.Widget):
   363 class UniversalChoiceEditor(widgets.Widget):
   368   """Edit interface for choice questions.
   364   """Edit interface for choice questions.
   369 
   365 
   598 
   594 
   599 
   595 
   600 def getRoleSpecificFields(survey, user, this_project, survey_form,
   596 def getRoleSpecificFields(survey, user, this_project, survey_form,
   601                           survey_record):
   597                           survey_record):
   602   """For evaluations, mentors get required Project and Grade fields, and
   598   """For evaluations, mentors get required Project and Grade fields, and
   603   students get a required Project field. 
   599   students get a required Project field.
   604 
   600 
   605   Because we need to get a list of the user's projects, we call the 
   601   Because we need to get a list of the user's projects, we call the
   606   logic getProjects method, which doubles as an access check.
   602   logic getProjects method, which doubles as an access check.
   607   (No projects means that the survey cannot be taken.)
   603   (No projects means that the survey cannot be taken.)
   608 
   604 
   609   params:
   605   params:
   610     survey: the survey being taken
   606     survey: the survey being taken
   613     survey_form: the surveyForm widget for this survey
   609     survey_form: the surveyForm widget for this survey
   614     survey_record: an existing survey record for a user-project-survey combo,
   610     survey_record: an existing survey record for a user-project-survey combo,
   615       or None
   611       or None
   616   """
   612   """
   617 
   613 
   618   from django import forms
       
   619 
       
   620   field_count = len(eval(survey.survey_content.schema).items())
   614   field_count = len(eval(survey.survey_content.schema).items())
   621   these_projects = survey_logic.getProjects(survey, user)
   615   these_projects = survey_logic.getProjects(survey, user)
   622   if not these_projects: return False # no projects found
   616   if not these_projects:
       
   617     return False # no projects found
   623 
   618 
   624   project_pairs = []
   619   project_pairs = []
   625   #insert a select field with options for each project
   620   #insert a select field with options for each project
   626   for project in these_projects:
   621   for project in these_projects:
   627     project_pairs.append((project.key(), project.title))
   622     project_pairs.append((project.key(), project.title))