app/soc/views/helper/surveys.py
changeset 2517 97117d341f62
parent 2507 4a6b8d6674b6
child 2518 66405056baf8
equal deleted inserted replaced
2516:afae8791ad97 2517:97117d341f62
    44 from soc.logic.lists import Lists
    44 from soc.logic.lists import Lists
    45 from soc.models.survey import COMMENT_PREFIX
    45 from soc.models.survey import COMMENT_PREFIX
    46 from soc.models.survey import SurveyContent
    46 from soc.models.survey import SurveyContent
    47 
    47 
    48 
    48 
       
    49 CHOICE_TYPES = set(('selection', 'pick_multi', 'choice', 'pick_quant'))
       
    50 
    49 # TODO(ajaksu) add this to template
    51 # TODO(ajaksu) add this to template
    50 REQUIRED_COMMENT_TPL = """
    52 REQUIRED_COMMENT_TPL = """
    51   <label for="required_for_{{ name }}">Required</label>
    53   <label for="required_for_{{ name }}">Required</label>
    52   <select id="required_for_{{ name }}" name="required_for_{{ name }}">
    54   <select id="required_for_{{ name }}" name="required_for_{{ name }}">
    53     <option value="True" {% if is_required %} selected='selected' {% endif %}
    55     <option value="True" {% if is_required %} selected='selected' {% endif %}
    64      {% endif %}>False</option>
    66      {% endif %}>False</option>
    65   </select><br/>
    67   </select><br/>
    66 """
    68 """
    67 
    69 
    68 
    70 
    69 class SurveyForm(djangoforms.ModelForm):
    71 class SurveyTakeForm(djangoforms.ModelForm):
    70   """Main SurveyContent form.
    72   """SurveyContent form for recording survey answers.
    71 
    73 
    72   This class is used to produce survey forms for several circumstances:
    74   This class is used to produce survey forms for survey taking:
    73     - Admin creating survey from scratch
       
    74     - Admin updating existing survey
       
    75     - User taking survey
    75     - User taking survey
    76     - User updating already taken survey
    76     - User updating already taken survey
    77 
    77 
    78   Using dynamic properties of the survey model (if passed as an arg) the
    78   Using dynamic properties of the survey model (if passed as an arg) the
    79   survey form is dynamically formed.
    79   survey form is dynamically formed.
    80   """
    80   """
    81 
    81 
    82   def __init__(self, *args, **kwargs):
    82   def __init__(self, *args, **kwargs):
    83     """Store special kwargs as attributes.
    83     """Store special kwargs as attributes.
    84 
    84 
       
    85       survey_content: a SuveryContent entity.
       
    86       survey_logic: instance of SurveyLogic.
       
    87       survey_record: a SurveyRecord entity.
    85       read_only: controls whether the survey taking UI allows data entry.
    88       read_only: controls whether the survey taking UI allows data entry.
    86       editing: controls whether to show the edit or show form.
       
    87     """
    89     """
    88 
    90 
    89     self.kwargs = kwargs
    91     self.kwargs = kwargs
       
    92 
    90     self.survey_content = self.kwargs.pop('survey_content', None)
    93     self.survey_content = self.kwargs.pop('survey_content', None)
    91     self.this_user = self.kwargs.pop('this_user', None)
       
    92     self.project = self.kwargs.pop('project', None)
       
    93     self.survey_logic = self.kwargs.pop('survey_logic', None)
    94     self.survey_logic = self.kwargs.pop('survey_logic', None)
    94     self.survey_record = self.kwargs.pop('survey_record', None)
    95     self.survey_record = self.kwargs.pop('survey_record', None)
    95 
       
    96     self.read_only = self.kwargs.pop('read_only', None)
    96     self.read_only = self.kwargs.pop('read_only', None)
    97     self.editing = self.kwargs.pop('editing', None)
       
    98 
    97 
    99     self.fields_map = dict(
    98     self.fields_map = dict(
   100         long_answer=self.addLongField,
    99         long_answer=self.addLongField,
   101         short_answer=self.addShortField,
   100         short_answer=self.addShortField,
   102         selection=self.addSingleField,
   101         selection=self.addSingleField,
   103         pick_multi=self.addMultiField,
   102         pick_multi=self.addMultiField,
   104         pick_quant=self.addQuantField,
   103         pick_quant=self.addQuantField,
   105         )
   104         )
   106 
   105 
   107     self.kwargs['data'] = {}
   106     self.kwargs['data'] = {}
   108     super(SurveyForm, self).__init__(*args, **self.kwargs)
   107     super(SurveyTakeForm, self).__init__(*args, **self.kwargs)
   109 
   108 
   110   def getFields(self, post_dict=None):
   109   def getFields(self, post_dict=None):
   111     """Build the SurveyContent (questions) form fields.
   110     """Build the SurveyContent (questions) form fields.
   112 
   111 
   113     params:
   112     params:
   121       return
   120       return
   122 
   121 
   123     post_dict = post_dict or {}
   122     post_dict = post_dict or {}
   124     self.survey_fields = {}
   123     self.survey_fields = {}
   125     schema = SurveyContentSchema(self.survey_content.schema)
   124     schema = SurveyContentSchema(self.survey_content.schema)
   126     has_record = (not self.editing) and (self.survey_record or post_dict)
   125     has_record = self.survey_record or post_dict
   127     extra_attrs = {}
   126     extra_attrs = {}
   128 
   127 
   129     # figure out whether we want a read-only view
   128     # figure out whether we want a read-only view
   130     if not self.editing:
   129     read_only = self.read_only
   131       # only survey taking can be read-only
   130 
   132       read_only = self.read_only
   131     if not read_only:
   133 
   132       survey_content = self.survey_content
   134       if not read_only:
   133       survey_entity = self.survey_logic.getSurveyForContent(survey_content)
   135         survey_content = self.survey_content
   134       deadline = survey_entity.survey_end
   136         survey_entity = self.survey_logic.getSurveyForContent(survey_content)
   135       read_only =  deadline and (datetime.datetime.now() > deadline)
   137         deadline = survey_entity.survey_end
   136     else:
   138         read_only =  deadline and (datetime.datetime.now() > deadline)
   137       extra_attrs['disabled'] = 'disabled'
   139       else:
       
   140         extra_attrs['disabled'] = 'disabled'
       
   141 
   138 
   142     # flag whether we can use getlist to retrieve multiple values
   139     # flag whether we can use getlist to retrieve multiple values
   143     is_post = hasattr(post_dict, 'getlist')
   140     is_post = hasattr(post_dict, 'getlist')
   144 
   141 
   145     # add unordered fields to self.survey_fields
   142     # add unordered fields to self.survey_fields
   180           value = value.split(',')
   177           value = value.split(',')
   181       elif from_content and is_multi:
   178       elif from_content and is_multi:
   182         value = []
   179         value = []
   183 
   180 
   184       # record field value for validation
   181       # record field value for validation
   185       if not from_content:
   182       #if not from_content:
   186         self.data[field] = value
   183       self.data[field] = value
   187 
   184 
   188       # find correct field type
   185       # find correct field type
   189       addField = self.fields_map[schema.getType(field)]
   186       addField = self.fields_map[schema.getType(field)]
   190 
   187 
   191       # check if question is required, it's never required when editing
   188       # check if question is required, it's never required when editing
   192       required = not self.editing and schema.getRequired(field)
   189       required = schema.getRequired(field)
   193       kwargs = dict(label=label, req=required)
   190       kwargs = dict(label=label, req=required)
   194 
   191 
   195       # add new field
   192       # add new field
   196       addField(field, value, extra_attrs, schema, **kwargs)
   193       addField(field, value, extra_attrs, schema, **kwargs)
   197 
   194 
   210 
   207 
   211     # first, insert dynamic survey fields
   208     # first, insert dynamic survey fields
   212     for position, property in survey_order.items():
   209     for position, property in survey_order.items():
   213       position = position * 2
   210       position = position * 2
   214       self.fields.insert(position, property, self.survey_fields[property])
   211       self.fields.insert(position, property, self.survey_fields[property])
   215       if not self.editing:
   212 
   216         # add comment if field has one and this isn't an edit view
   213       # add comment if field has one and this isn't an edit view
   217         property = COMMENT_PREFIX + property
   214       property = COMMENT_PREFIX + property
   218         if property in self.survey_fields:
   215       if property in self.survey_fields:
   219           self.fields.insert(position - 1, property,
   216         self.fields.insert(position - 1, property,
   220                              self.survey_fields[property])
   217                            self.survey_fields[property])
   221     return self.fields
   218     return self.fields
   222 
   219 
   223   def addLongField(self, field, value, attrs, schema, req=True, label='',
   220   def addLongField(self, field, value, attrs, schema, req=True, label='',
   224                    tip='', comment=''):
   221                    tip='', comment=''):
   225     """Add a long answer fields to this form.
   222     """Add a long answer fields to this form.
   233       label: label for field
   230       label: label for field
   234       tip: tooltip text for field
   231       tip: tooltip text for field
   235       comment: initial comment value for field
   232       comment: initial comment value for field
   236     """
   233     """
   237 
   234 
   238     # use a widget that allows setting required and comments
   235     widget = widgets.Textarea(attrs=attrs)
   239     has_comment = schema.getHasComment(field)
       
   240     is_required = schema.getRequired(field)
       
   241     widget = LongTextarea(is_required, has_comment, attrs=attrs,
       
   242                           editing=self.editing)
       
   243 
   236 
   244     if not tip:
   237     if not tip:
   245       tip = 'Please provide a long answer to this question.'
   238       tip = 'Please provide a long answer to this question.'
   246 
   239 
   247     question = CharField(help_text=tip, required=req, label=label,
   240     question = CharField(help_text=tip, required=req, label=label,
   264       comment: initial comment value for field
   257       comment: initial comment value for field
   265     """
   258     """
   266 
   259 
   267     attrs['class'] = "text_question"
   260     attrs['class'] = "text_question"
   268 
   261 
   269     # use a widget that allows setting required and comments
   262     widget = widgets.TextInput(attrs=attrs)
   270     has_comment = schema.getHasComment(field)
       
   271     is_required = schema.getRequired(field)
       
   272     widget = ShortTextInput(is_required, has_comment, attrs=attrs,
       
   273                           editing=self.editing)
       
   274 
   263 
   275     if not tip:
   264     if not tip:
   276       tip = 'Please provide a short answer to this question.'
   265       tip = 'Please provide a short answer to this question.'
   277 
   266 
   278     question = CharField(help_text=tip, required=req, label=label,
   267     question = CharField(help_text=tip, required=req, label=label,
   293       label: label for field
   282       label: label for field
   294       tip: tooltip text for field
   283       tip: tooltip text for field
   295       comment: initial comment value for field
   284       comment: initial comment value for field
   296     """
   285     """
   297 
   286 
   298     widget = schema.getWidget(field, self.editing, attrs)
   287     widget = PickOneSelect(attrs)
   299 
   288 
   300     these_choices = []
   289     these_choices = []
   301     # add all properties, but select chosen one
   290     # add all properties, but select chosen one
       
   291     # TODO(ajaksu): this breaks ordering and blocks merging choice methods
   302     options = getattr(self.survey_content, field)
   292     options = getattr(self.survey_content, field)
   303     has_record = not self.editing and self.survey_record
   293     has_record = not self.editing and self.survey_record
   304     if has_record and hasattr(self.survey_record, field):
   294     if has_record and hasattr(self.survey_record, field):
   305       these_choices.append((value, value))
   295       these_choices.append((value, value))
   306       if value in options:
   296       if value in options:
   330       tip: tooltip text for field
   320       tip: tooltip text for field
   331       comment: initial comment value for field
   321       comment: initial comment value for field
   332 
   322 
   333     """
   323     """
   334 
   324 
   335     widget = schema.getWidget(field, self.editing, attrs)
   325     widget = PickManyCheckbox(attrs)
   336 
   326 
   337     # TODO(ajaksu) need to allow checking checkboxes by default
   327     # TODO(ajaksu) need to allow checking checkboxes by default
   338     if self.survey_record and isinstance(value, basestring):
   328     if self.survey_record and isinstance(value, basestring):
   339       # pass value as 'initial' so MultipleChoiceField renders checked boxes
   329       # pass value as 'initial' so MultipleChoiceField renders checked boxes
   340       value = value.split(',')
   330       value = value.split(',')
   363       tip: tooltip text for field
   353       tip: tooltip text for field
   364       comment: initial comment value for field
   354       comment: initial comment value for field
   365 
   355 
   366     """
   356     """
   367 
   357 
   368     widget = schema.getWidget(field, self.editing, attrs)
   358     widget = PickQuantRadio(attrs)
   369 
   359 
   370     if self.survey_record:
   360     if self.survey_record:
   371       value = value
   361       value = value
   372     else:
   362     else:
   373       value = None
   363       value = None
   380                              choices=tuple(these_choices), widget=widget,
   370                              choices=tuple(these_choices), widget=widget,
   381                              initial=value)
   371                              initial=value)
   382     self.survey_fields[field] = question
   372     self.survey_fields[field] = question
   383 
   373 
   384   def addCommentField(self, field, comment, attrs, tip):
   374   def addCommentField(self, field, comment, attrs, tip):
   385     if not self.editing:
   375     widget = widgets.Textarea(attrs=attrs)
   386       widget = widgets.Textarea(attrs=attrs)
   376     comment_field = CharField(help_text=tip, required=False, label='Comments',
   387       comment_field = CharField(help_text=tip, required=False, label='Comments',
   377                         widget=widget, initial=comment)
   388                           widget=widget, initial=comment)
   378     self.survey_fields[COMMENT_PREFIX + field] = comment_field
   389       self.survey_fields[COMMENT_PREFIX + field] = comment_field
   379 
   390 
   380 
   391   class Meta(object):
   381   class Meta(object):
   392     model = SurveyContent
   382     model = SurveyContent
   393     exclude = ['schema']
   383     exclude = ['schema']
   394 
   384 
   395 
   385 
       
   386 class SurveyEditForm(djangoforms.ModelForm):
       
   387   """SurveyContent form for editing a survey.
       
   388 
       
   389   This class is used to produce survey forms for several circumstances:
       
   390     - Admin creating survey from scratch
       
   391     - Admin updating existing survey
       
   392 
       
   393   Using dynamic properties of the survey model (if passed as an arg) the
       
   394   survey form is dynamically formed.
       
   395   """
       
   396 
       
   397   def __init__(self, *args, **kwargs):
       
   398     """Store special kwargs as attributes.
       
   399 
       
   400       survey_content: a SurveyContent entity.
       
   401       survey_logic: an instance of SurveyLogic.
       
   402     """
       
   403 
       
   404     self.kwargs = kwargs
       
   405     self.survey_content = self.kwargs.pop('survey_content', None)
       
   406     self.survey_logic = self.kwargs.pop('survey_logic', None)
       
   407 
       
   408     super(SurveyEditForm, self).__init__(*args, **self.kwargs)
       
   409 
       
   410   def getFields(self):
       
   411     """Build the SurveyContent (questions) form fields.
       
   412 
       
   413     params:
       
   414       post_dict: dict with POST data that will be used for validation
       
   415 
       
   416     Populates self.survey_fields, which will be ordered in self.insert_fields.
       
   417     Also populates self.data, which will be used in form validation.
       
   418     """
       
   419 
       
   420     if not self.survey_content:
       
   421       return
       
   422 
       
   423     self.survey_fields = {}
       
   424     schema = SurveyContentSchema(self.survey_content.schema)
       
   425     extra_attrs = {}
       
   426 
       
   427     # add unordered fields to self.survey_fields
       
   428     for field in self.survey_content.dynamic_properties():
       
   429 
       
   430       # use prompts set by survey creator
       
   431       value = getattr(self.survey_content, field)
       
   432       from_content = True
       
   433 
       
   434       label = schema.getLabel(field)
       
   435       if label is None:
       
   436         continue
       
   437 
       
   438       tip = 'Please provide an answer to this question.'
       
   439       kwargs = schema.getEditFieldArgs(field, value, tip, label)
       
   440 
       
   441       kwargs['widget'] = schema.getEditWidget(field, extra_attrs)
       
   442 
       
   443 
       
   444       # add new field
       
   445       self.survey_fields[field] = schema.getEditField(field)(**kwargs)
       
   446 
       
   447     # TODO(ajaksu): find a new way to keep fields in order
       
   448     return self.insertFields()
       
   449 
       
   450   def insertFields(self):
       
   451     """Add ordered fields to self.fields.
       
   452     """
       
   453 
       
   454     survey_order = self.survey_content.getSurveyOrder()
       
   455 
       
   456     # insert dynamic survey fields
       
   457     for position, property in survey_order.items():
       
   458       self.fields.insert(position, property, self.survey_fields[property])
       
   459 
       
   460     return self.fields
       
   461 
       
   462   class Meta(object):
       
   463     model = SurveyContent
       
   464     exclude = ['schema']
       
   465 
       
   466 
   396 class SurveyContentSchema(object):
   467 class SurveyContentSchema(object):
   397   """Abstract question metadata handling.
   468   """Abstract question metadata handling.
   398   """
   469   """
   399 
   470 
   400   def __init__(self, schema):
   471   def __init__(self, schema):
       
   472     """Set the dictionary that this class encapsulates.
       
   473 
       
   474     Args:
       
   475       schema: schema as stored in SurveyConent entity
       
   476     """
       
   477 
   401     self.schema = eval(schema)
   478     self.schema = eval(schema)
   402 
   479 
   403   def getType(self, field):
   480   def getType(self, field):
       
   481     """Fetch question type for field e.g. short_answer, pick_multi, etc.
       
   482 
       
   483     Args:
       
   484       field: name of the field to get the type from
       
   485     """
       
   486 
   404     return self.schema[field]["type"]
   487     return self.schema[field]["type"]
   405 
   488 
   406   def getRequired(self, field):
   489   def getRequired(self, field):
   407     """Check whether survey question is required.
   490     """Check whether survey question is required.
       
   491 
       
   492     Args:
       
   493       field: name of the field to check the required property for
   408     """
   494     """
   409 
   495 
   410     return self.schema[field]["required"]
   496     return self.schema[field]["required"]
   411 
   497 
   412   def getHasComment(self, field):
   498   def getHasComment(self, field):
   413     """Check whether survey question allows adding a comment.
   499     """Check whether survey question allows adding a comment.
       
   500 
       
   501     Args:
       
   502       field: name of the field to get the hasComment property for
   414     """
   503     """
   415 
   504 
   416     return self.schema[field]["has_comment"]
   505     return self.schema[field]["has_comment"]
   417 
   506 
   418   def getRender(self, field):
   507   def getRender(self, field):
       
   508     """Get rendering options for choice questions.
       
   509 
       
   510     Args:
       
   511       field: name of the field to get the rendering option for
       
   512     """
       
   513 
   419     return self.schema[field]["render"]
   514     return self.schema[field]["render"]
   420 
   515 
   421   def getWidget(self, field, editing, attrs):
   516   def getEditField(self, field):
   422     """Get survey editing or taking widget for choice questions.
   517     """For a given question kind, get the correct edit view field.
   423     """
   518     """
   424 
   519 
   425     if editing:
   520     kind = self.getType(field)
   426       kind = self.getType(field)
   521     if kind in CHOICE_TYPES:
       
   522       Field = PickOneField
       
   523     else:
       
   524       Field = CharField
       
   525 
       
   526     return Field
       
   527 
       
   528   def getEditFieldArgs(self, field, value, tip, label):
       
   529     """Build edit view field arguments.
       
   530 
       
   531     params:
       
   532       field: field name
       
   533       value: field value (text for text questions, list for choice questions)
       
   534       tipe: help text, to be used in a tooltip
       
   535       label: the field's question (or identifier if question is missing)
       
   536     """
       
   537 
       
   538     kind = self.getType(field)
       
   539 
       
   540     kwargs = dict(help_text=tip, required=False, label=label)
       
   541 
       
   542     if kind in CHOICE_TYPES:
       
   543       kwargs['choices'] = tuple([(val, val) for val in value])
       
   544     else:
       
   545       kwargs['initial'] = value
       
   546 
       
   547     return kwargs
       
   548 
       
   549   def getEditWidget(self, field, attrs):
       
   550     """Get survey editing widget for questions.
       
   551     """
       
   552 
       
   553     kind = self.getType(field)
       
   554     is_required = self.getRequired(field)
       
   555     has_comment = self.getHasComment(field)
       
   556 
       
   557     if kind in CHOICE_TYPES:
       
   558       widget = UniversalChoiceEditor
   427       render = self.getRender(field)
   559       render = self.getRender(field)
   428       is_required = self.getRequired(field)
   560       args = kind, render, is_required, has_comment
   429       has_comment = self.getHasComment(field)
       
   430       widget = UniversalChoiceEditor(kind, render, is_required, has_comment)
       
   431     else:
   561     else:
   432       widget = WIDGETS[self.schema[field]['render']](attrs=attrs)
   562       args = is_required, has_comment
   433     return widget
   563       if kind == 'long_answer':
       
   564         attrs['class'] = "text_question"
       
   565         widget = LongTextarea
       
   566       elif kind == 'short_answer':
       
   567         widget = ShortTextInput
       
   568 
       
   569     kwargs = dict(attrs=attrs)
       
   570 
       
   571     return widget(*args, **kwargs)
   434 
   572 
   435   def getLabel(self, field):
   573   def getLabel(self, field):
   436     """Fetch the free text 'question' or use field name as label.
   574     """Fetch the free text 'question' or use field name as label.
   437     """
   575     """
   438 
   576 
   545 
   683 
   546 class LongTextarea(widgets.Textarea):
   684 class LongTextarea(widgets.Textarea):
   547   """Set whether long question is required or allows comments.
   685   """Set whether long question is required or allows comments.
   548   """
   686   """
   549 
   687 
   550   def __init__(self, is_required, has_comment, attrs=None, editing=False):
   688   def __init__(self, is_required, has_comment, attrs=None):
   551     """Initialize widget and store editing mode.
   689     """Initialize widget and store editing mode.
   552 
   690 
   553     params:
   691     params:
   554       is_required: bool, controls selection in the 'required' extra field
   692       is_required: bool, controls selection in the 'required' extra field
   555       has_comments: bool, controls selection in the 'has_comment' extra field
   693       has_comments: bool, controls selection in the 'has_comment' extra field
   556       editing: bool, controls rendering as plain textarea or with extra fields
   694       editing: bool, controls rendering as plain textarea or with extra fields
   557     """
   695     """
   558 
   696 
   559     self.editing = editing
       
   560     self.is_required = is_required
   697     self.is_required = is_required
   561     self.has_comment = has_comment
   698     self.has_comment = has_comment
   562 
   699 
   563     super(LongTextarea, self).__init__(attrs)
   700     super(LongTextarea, self).__init__(attrs)
   564 
   701 
   569     """
   706     """
   570 
   707 
   571     # plain text area
   708     # plain text area
   572     output = super(LongTextarea, self).render(name, value, attrs)
   709     output = super(LongTextarea, self).render(name, value, attrs)
   573 
   710 
   574     if self.editing:
   711     # add 'required' and 'has_comment' fields
   575       # add 'required' and 'has_comment' fields
   712     context = dict(name=name, is_required=self.is_required,
   576       context = dict(name=name, is_required=self.is_required,
   713                    has_comment=self.has_comment)
   577                      has_comment=self.has_comment)
   714     template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
   578       template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
   715     rendered = template.render(context=loader.Context(dict_=context))
   579       rendered = template.render(context=loader.Context(dict_=context))
   716     output =  rendered + output
   580       output =  rendered + output
   717 
   581 
   718     output = '<fieldset>' + output + '</fieldset>'
   582       output = '<fieldset>' + output + '</fieldset>'
       
   583     return output
   719     return output
   584 
   720 
   585 
   721 
   586 class ShortTextInput(widgets.TextInput):
   722 class ShortTextInput(widgets.TextInput):
   587   """Set whether short answer question is required or allows comments.
   723   """Set whether short answer question is required or allows comments.
   588   """
   724   """
   589 
   725 
   590   def __init__(self, is_required, has_comment, attrs=None, editing=False):
   726   def __init__(self, is_required, has_comment, attrs=None):
   591     """Initialize widget and store editing mode.
   727     """Initialize widget and store editing mode.
   592 
   728 
   593     params:
   729     params:
   594       is_required: bool, controls selection in the 'required' extra field
   730       is_required: bool, controls selection in the 'required' extra field
   595       has_comments: bool, controls selection in the 'has_comment' extra field
   731       has_comments: bool, controls selection in the 'has_comment' extra field
   596       editing: bool, controls rendering as plain text input or with extra fields
   732       editing: bool, controls rendering as plain text input or with extra fields
   597     """
   733     """
   598 
   734 
   599     self.editing = editing
       
   600     self.is_required = is_required
   735     self.is_required = is_required
   601     self.has_comment = has_comment
   736     self.has_comment = has_comment
   602 
   737 
   603     super(ShortTextInput, self).__init__(attrs)
   738     super(ShortTextInput, self).__init__(attrs)
   604 
   739 
   609     """
   744     """
   610 
   745 
   611     # plain text area
   746     # plain text area
   612     output = super(ShortTextInput, self).render(name, value, attrs)
   747     output = super(ShortTextInput, self).render(name, value, attrs)
   613 
   748 
   614     if self.editing:
   749     # add 'required' and 'has_comment' fields
   615       # add 'required' and 'has_comment' fields
   750     context = dict(name=name, is_required=self.is_required,
   616       context = dict(name=name, is_required=self.is_required,
   751                    has_comment=self.has_comment)
   617                      has_comment=self.has_comment)
   752     template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
   618       template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
   753     rendered = template.render(context=loader.Context(dict_=context))
   619       rendered = template.render(context=loader.Context(dict_=context))
   754     output =  rendered + output
   620       output =  rendered + output
   755 
   621 
   756     output = '<fieldset>' + output + '</fieldset>'
   622       output = '<fieldset>' + output + '</fieldset>'
       
   623     return output
   757     return output
   624 
   758 
   625 
   759 
   626 class PickOneSelect(forms.Select):
   760 class PickOneSelect(forms.Select):
   627   """Stub for customizing the single choice select widget.
   761   """Stub for customizing the single choice select widget.
   778     markup = loader.render_to_string('soc/survey/results.html',
   912     markup = loader.render_to_string('soc/survey/results.html',
   779                                      dictionary=context).strip('\n')
   913                                      dictionary=context).strip('\n')
   780     return markup
   914     return markup
   781 
   915 
   782 
   916 
   783 def getRoleSpecificFields(survey, user, this_project, survey_form,
       
   784                           survey_record):
       
   785   """For evaluations, mentors get required Project and Grade fields, and
       
   786   students get a required Project field.
       
   787 
       
   788   Because we need to get a list of the user's projects, we call the
       
   789   logic getProjects method, which doubles as an access check.
       
   790   (No projects means that the survey cannot be taken.)
       
   791 
       
   792   params:
       
   793     survey: the survey being taken
       
   794     user: the survey-taking user
       
   795     this_project: either an already-selected project, or None
       
   796     survey_form: the surveyForm widget for this survey
       
   797     survey_record: an existing survey record for a user-project-survey combo,
       
   798       or None
       
   799   """
       
   800 
       
   801   field_count = len(eval(survey.survey_content.schema).items())
       
   802   these_projects = survey_logic.getProjects(survey, user)
       
   803   if not these_projects:
       
   804     return False # no projects found
       
   805 
       
   806   project_pairs = []
       
   807   #insert a select field with options for each project
       
   808   for project in these_projects:
       
   809     project_pairs.append((project.key(), project.title))
       
   810   if project_pairs:
       
   811     project_tuples = tuple(project_pairs)
       
   812     # add select field containing list of projects
       
   813     projectField =  forms.fields.ChoiceField(
       
   814                               choices=project_tuples,
       
   815                               required=True,
       
   816                               widget=forms.Select())
       
   817     projectField.choices.insert(0, (None, "Choose a Project")  )
       
   818     # if editing an existing survey
       
   819     if not this_project and survey_record:
       
   820       this_project = survey_record.project
       
   821     if this_project:
       
   822       for tup in project_tuples:
       
   823         if tup[1] == this_project.title:
       
   824           if survey_record: project_name = tup[1] + " (Saved)"
       
   825           else: project_name = tup[1]
       
   826           projectField.choices.remove(tup)
       
   827           projectField.choices.insert(0, (tup[0], project_name)  )
       
   828           break
       
   829     survey_form.fields.insert(0, 'project', projectField )
       
   830 
       
   831   if survey.taking_access == "mentor evaluation":
       
   832     # If this is a mentor, add a field
       
   833     # determining if student passes or fails.
       
   834     # Activate grades handler should determine whether new status
       
   835     # is midterm_passed, final_passed, etc.
       
   836     grade_choices = (('pass', 'Pass'), ('fail', 'Fail'))
       
   837     grade_vals = { 'pass': True, 'fail': False }
       
   838     gradeField = forms.fields.ChoiceField(choices=grade_choices,
       
   839                                            required=True,
       
   840                                            widget=forms.Select())
       
   841 
       
   842     gradeField.choices.insert(0, (None, "Choose a Grade")  )
       
   843     if survey_record:
       
   844       for g in grade_choices:
       
   845         if grade_vals[g[0]] == survey_record.grade:
       
   846           gradeField.choices.insert(0, (g[0],g[1] + " (Saved)")   )
       
   847           gradeField.choices.remove(g)
       
   848           break;
       
   849       gradeField.show_hidden_initial = True
       
   850 
       
   851     survey_form.fields.insert(field_count + 1, 'grade', gradeField)
       
   852 
       
   853   return survey_form
       
   854 
       
   855 
       
   856 class HelperForm(object):
   917 class HelperForm(object):
   857   """Thin wrapper for adding values to params['edit_form'].fields.
   918   """Thin wrapper for adding values to params['edit_form'].fields.
   858   """
   919   """
   859 
   920 
   860   def __init__(self, form=None):
   921   def __init__(self, form=None):