app/soc/views/helper/surveys.py
changeset 2432 636dfd5381c2
child 2439 7fac0da44bbf
equal deleted inserted replaced
2431:800a020c9bcf 2432:636dfd5381c2
       
     1 #!/usr/bin/python2.5
       
     2 #
       
     3 # Copyright 2009 the Melange authors.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #   http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 
       
    17 """Custom widgets used for Survey form fields, plus the SurveyContent form.
       
    18 """
       
    19 
       
    20 __authors__ = [
       
    21   '"Daniel Diniz" <ajaksu@gmail.com>',
       
    22   '"James Levy" <jamesalexanderlevy@gmail.com>',
       
    23   '"Lennard de Rijk" <ljvderijk@gmail.com>',
       
    24   ]
       
    25 
       
    26 
       
    27 from itertools import chain
       
    28 import datetime
       
    29 
       
    30 from google.appengine.ext.db import djangoforms
       
    31 
       
    32 from django import forms
       
    33 from django.forms import widgets
       
    34 from django.forms.fields import CharField
       
    35 from django.template import loader
       
    36 from django.utils.encoding import force_unicode
       
    37 from django.utils.html import escape
       
    38 from django.utils.safestring import mark_safe
       
    39 
       
    40 from soc.logic import dicts
       
    41 from soc.logic.lists import Lists
       
    42 from soc.logic.models.survey import logic as survey_logic
       
    43 from soc.logic.models.survey import results_logic
       
    44 from soc.models.survey import SurveyContent
       
    45 
       
    46 
       
    47 class SurveyForm(djangoforms.ModelForm):
       
    48   """Main SurveyContent form.
       
    49 
       
    50   This class is used to produce survey forms for several circumstances:
       
    51     - Admin creating survey from scratch
       
    52     - Admin updating existing survey
       
    53     - User taking survey
       
    54     - User updating already taken survey
       
    55 
       
    56   Using dynamic properties of the survey model (if passed as an arg) the
       
    57   survey form is dynamically formed.
       
    58   """
       
    59 
       
    60   def __init__(self, *args, **kwargs):
       
    61     """Store special kwargs as attributes.
       
    62 
       
    63       read_only: controls whether the survey taking UI allows data entry.
       
    64       editing: controls whether to show the edit or show form.
       
    65     """
       
    66 
       
    67     self.kwargs = kwargs
       
    68     self.survey_content = self.kwargs.get('survey_content', None)
       
    69     self.this_user = self.kwargs.get('this_user', None)
       
    70     self.project = self.kwargs.get('project', None)
       
    71     self.survey_record = self.kwargs.get('survey_record', None)
       
    72 
       
    73     del self.kwargs['survey_content']
       
    74     del self.kwargs['this_user']
       
    75     del self.kwargs['project']
       
    76     del self.kwargs['survey_record']
       
    77 
       
    78     self.read_only = self.kwargs.get('read_only', None)
       
    79     if 'read_only' in self.kwargs:
       
    80       del self.kwargs['read_only']
       
    81 
       
    82     self.editing = self.kwargs.get('editing', None)
       
    83     if 'editing' in self.kwargs:
       
    84       del self.kwargs['editing']
       
    85 
       
    86     super(SurveyForm, self).__init__(*args, **self.kwargs)
       
    87 
       
    88   def getFields(self):
       
    89     """Build the SurveyContent (questions) form fields.
       
    90 
       
    91     Populates self.survey_fields, which will be ordered in self.insert_fields.
       
    92     """
       
    93 
       
    94     if not self.survey_content:
       
    95       return
       
    96 
       
    97     self.survey_fields = {}
       
    98     schema = eval(self.survey_content.schema)
       
    99     has_record = (not self.editing) and self.survey_record
       
   100     extra_attrs = {}
       
   101 
       
   102     # figure out whether we want a read-only view
       
   103     if not self.editing:
       
   104       # only survey taking can be read-only
       
   105       read_only = self.read_only
       
   106 
       
   107       if not read_only:
       
   108         deadline = self.survey_content.survey_parent.get().deadline
       
   109         read_only =  deadline and (datetime.datetime.now() > deadline)
       
   110       else:
       
   111         extra_attrs['disabled'] = 'disabled'
       
   112 
       
   113     # add unordered fields to self.survey_fields
       
   114     for field in self.survey_content.dynamic_properties():
       
   115 
       
   116       # a comment made by the user
       
   117       comment = ''
       
   118       if has_record and hasattr(self.survey_record, field):
       
   119         # previously entered value
       
   120         value = getattr(self.survey_record, field)
       
   121         if hasattr(self.survey_record, 'comment_for_' + field):
       
   122           comment = getattr(self.survey_record, 'comment_for_' + field)
       
   123       else:
       
   124         # use prompts set by survey creator
       
   125         value = getattr(self.survey_content, field)
       
   126 
       
   127       if field not in schema:
       
   128         logging.error('field %s not found in schema %s' %
       
   129         (field, str(schema) ) )
       
   130         continue 
       
   131       elif 'question' in schema[field]:
       
   132         label = schema[field].get('question', None) or field
       
   133       else:
       
   134         label = field
       
   135 
       
   136       # dispatch to field-specific methods
       
   137       if schema[field]["type"] == "long_answer":
       
   138         self.addLongField(field, value, extra_attrs, label=label,
       
   139                           comment=comment)
       
   140       elif schema[field]["type"] == "short_answer":
       
   141         self.addShortField(field, value, extra_attrs, label=label,
       
   142                            comment=comment)
       
   143       elif schema[field]["type"] == "selection":
       
   144         self.addSingleField(field, value, extra_attrs, schema, label=label,
       
   145                             comment=comment)
       
   146       elif schema[field]["type"] == "pick_multi":
       
   147         self.addMultiField(field, value, extra_attrs, schema, label=label,
       
   148                            comment=comment)
       
   149       elif schema[field]["type"] == "pick_quant":
       
   150         self.addQuantField(field, value, extra_attrs, schema, label=label,
       
   151                            comment=comment)
       
   152 
       
   153     return self.insertFields()
       
   154 
       
   155   def insertFields(self):
       
   156     """Add ordered fields to self.fields.
       
   157     """
       
   158 
       
   159     survey_order = self.survey_content.getSurveyOrder()
       
   160 
       
   161     # first, insert dynamic survey fields
       
   162     for position, property in survey_order.items():
       
   163       position = position * 2
       
   164       self.fields.insert(position, property, self.survey_fields[property])
       
   165       if not self.editing:
       
   166         property = 'comment_for_' + property
       
   167         self.fields.insert(position - 1, property,
       
   168                            self.survey_fields[property])
       
   169     return self.fields
       
   170 
       
   171   def addLongField(self, field, value, attrs, req=False, label='', tip='',
       
   172                    comment=''):
       
   173     """Add a long answer fields to this form.
       
   174 
       
   175     params:
       
   176       field: the current field
       
   177       value: the initial value for this field
       
   178       attrs: additional attributes for field
       
   179       req: required bool
       
   180       label: label for field
       
   181       tip: tooltip text for field
       
   182       comment: initial comment value for field 
       
   183     """
       
   184 
       
   185     widget = widgets.Textarea(attrs=attrs)
       
   186 
       
   187     if not tip:
       
   188       tip = 'Please provide a long answer to this question.'
       
   189 
       
   190     question = CharField(help_text=tip, required=req, label=label,
       
   191                          widget=widget, initial=value)
       
   192     self.survey_fields[field] = question
       
   193 
       
   194     if not self.editing:
       
   195       widget = widgets.Textarea(attrs=attrs)
       
   196       comment = CharField(help_text=tip, required=False, label='Comments',
       
   197                           widget=widget, initial=comment)
       
   198       self.survey_fields['comment_for_' + field] = comment
       
   199 
       
   200   def addShortField(self, field, value, attrs, req=False, label='', tip='',
       
   201                     comment=''):
       
   202     """Add a short answer fields to this form.
       
   203 
       
   204     params:
       
   205       field: the current field
       
   206       value: the initial value for this field
       
   207       attrs: additional attributes for field
       
   208       req: required bool
       
   209       label: label for field
       
   210       tip: tooltip text for field
       
   211       comment: initial comment value for field 
       
   212     """
       
   213 
       
   214     attrs['class'] = "text_question"
       
   215     widget = widgets.TextInput(attrs=attrs)
       
   216 
       
   217     if not tip:
       
   218       tip = 'Please provide a short answer to this question.'
       
   219 
       
   220     question = CharField(help_text=tip, required=req, label=label,
       
   221                          widget=widget, max_length=140, initial=value)
       
   222     self.survey_fields[field] = question
       
   223 
       
   224     if not self.editing:
       
   225       widget = widgets.Textarea(attrs=attrs)
       
   226       comment = CharField(help_text=tip, required=False, label='Comments',
       
   227                           widget=widget, initial=comment)
       
   228       self.survey_fields['comment_for_' + field] = comment
       
   229 
       
   230   def addSingleField(self, field, value, attrs, schema, req=False, label='',
       
   231                      tip='', comment=''):
       
   232     """Add a selection field to this form.
       
   233 
       
   234     params:
       
   235       field: the current field
       
   236       value: the initial value for this field
       
   237       attrs: additional attributes for field
       
   238       schema: schema for survey
       
   239       req: required bool
       
   240       label: label for field
       
   241       tip: tooltip text for field
       
   242       comment: initial comment value for field
       
   243     """
       
   244 
       
   245     if self.editing:
       
   246       kind = schema[field]["type"]
       
   247       render = schema[field]["render"]
       
   248       widget = UniversalChoiceEditor(kind, render)
       
   249     else:
       
   250       widget = WIDGETS[schema[field]['render']](attrs=attrs)
       
   251 
       
   252     these_choices = []
       
   253     # add all properties, but select chosen one
       
   254     options = getattr(self.survey_content, field)
       
   255     has_record = not self.editing and self.survey_record
       
   256     if has_record and hasattr(self.survey_record, field):
       
   257       these_choices.append((value, value))
       
   258       if value in options:
       
   259         options.remove(value)
       
   260 
       
   261     for option in options:
       
   262       these_choices.append((option, option))
       
   263     if not tip:
       
   264       tip = 'Please select an answer this question.'
       
   265 
       
   266     question = PickOneField(help_text=tip, required=req, label=label,
       
   267                             choices=tuple(these_choices), widget=widget)
       
   268     self.survey_fields[field] = question
       
   269 
       
   270     if not self.editing:
       
   271       widget = widgets.Textarea(attrs=attrs)
       
   272       comment = CharField(help_text=tip, required=False, label='Comments',
       
   273                           widget=widget, initial=comment)
       
   274       self.survey_fields['comment_for_' + field] = comment
       
   275 
       
   276   def addMultiField(self, field, value, attrs, schema, req=False, label='',
       
   277                     tip='', comment=''):
       
   278     """Add a pick_multi field to this form.
       
   279 
       
   280     params:
       
   281       field: the current field
       
   282       value: the initial value for this field
       
   283       attrs: additional attributes for field
       
   284       schema: schema for survey
       
   285       req: required bool
       
   286       label: label for field
       
   287       tip: tooltip text for field
       
   288       comment: initial comment value for field
       
   289     """
       
   290 
       
   291     if self.editing:
       
   292       kind = schema[field]["type"]
       
   293       render = schema[field]["render"]
       
   294       widget = UniversalChoiceEditor(kind, render)
       
   295     else:
       
   296       widget = WIDGETS[schema[field]['render']](attrs=attrs)
       
   297 
       
   298     # TODO(ajaksu) need to allow checking checkboxes by default
       
   299     if self.survey_record and isinstance(value, basestring):
       
   300       # pass value as 'initial' so MultipleChoiceField renders checked boxes
       
   301       value = value.split(',')
       
   302     else:
       
   303       value = None
       
   304 
       
   305     these_choices = [(v,v) for v in getattr(self.survey_content, field)]
       
   306     if not tip:
       
   307       tip = 'Please select one or more of these choices.'
       
   308 
       
   309     question = PickManyField(help_text=tip, required=req, label=label,
       
   310                              choices=tuple(these_choices), widget=widget,
       
   311                              initial=value)
       
   312     self.survey_fields[field] = question
       
   313     if not self.editing:
       
   314       widget = widgets.Textarea(attrs=attrs)
       
   315       comment = CharField(help_text=tip, required=False, label='Comments',
       
   316                           widget=widget, initial=comment)
       
   317       self.survey_fields['comment_for_' + field] = comment
       
   318 
       
   319   def addQuantField(self, field, value, attrs, schema, req=False, label='',
       
   320                     tip='', comment=''):
       
   321     """Add a pick_quant field to this form.
       
   322 
       
   323     params:
       
   324       field: the current field
       
   325       value: the initial value for this field
       
   326       attrs: additional attributes for field
       
   327       schema: schema for survey
       
   328       req: required bool
       
   329       label: label for field
       
   330       tip: tooltip text for field
       
   331       comment: initial comment value for field
       
   332     """
       
   333 
       
   334     if self.editing:
       
   335       kind = schema[field]["type"]
       
   336       render = schema[field]["render"]
       
   337       widget = UniversalChoiceEditor(kind, render)
       
   338     else:
       
   339       widget = WIDGETS[schema[field]['render']](attrs=attrs)
       
   340 
       
   341     if self.survey_record:
       
   342       value = value
       
   343     else:
       
   344       value = None
       
   345 
       
   346     these_choices = [(v,v) for v in getattr(self.survey_content, field)]
       
   347     if not tip:
       
   348       tip = 'Please select one of these choices.'
       
   349 
       
   350     question = PickQuantField(help_text=tip, required=req, label=label,
       
   351                              choices=tuple(these_choices), widget=widget,
       
   352                              initial=value)
       
   353     self.survey_fields[field] = question
       
   354     if not self.editing:
       
   355       widget = widgets.Textarea(attrs=attrs)
       
   356       comment = CharField(help_text=tip, required=False, label='Comments',
       
   357                           widget=widget, initial=comment)
       
   358       self.survey_fields['comment_for_' + field] = comment
       
   359 
       
   360 
       
   361   class Meta(object):
       
   362     model = SurveyContent
       
   363     exclude = ['schema']
       
   364 
       
   365 
       
   366 class UniversalChoiceEditor(widgets.Widget):
       
   367   """Edit interface for choice questions.
       
   368 
       
   369   Allows adding and removing options, re-ordering and editing option text.
       
   370   """
       
   371 
       
   372   def __init__(self, kind, render, attrs=None, choices=()):
       
   373 
       
   374     self.attrs = attrs or {}
       
   375 
       
   376     # Choices can be any iterable, but we may need to render this widget
       
   377     # multiple times. Thus, collapse it into a list so it can be consumed
       
   378     # more than once.
       
   379     self.choices = list(choices)
       
   380     self.kind = kind
       
   381     self.render_as = render
       
   382 
       
   383   def render(self, name, value, attrs=None, choices=()):
       
   384     """ renders UCE widget
       
   385     """
       
   386 
       
   387     if value is None:
       
   388       value = ''
       
   389 
       
   390     final_attrs = self.build_attrs(attrs, name=name)
       
   391 
       
   392     # find out which options should be selected in type and render drop-downs.
       
   393     selected = 'selected="selected"'
       
   394     context =  dict(
       
   395         name=name,
       
   396         is_selection=selected * (self.kind == 'selection'),
       
   397         is_pick_multi=selected * (self.kind == 'pick_multi'),
       
   398         is_pick_quant=selected * (self.kind == 'pick_quant'),
       
   399         is_select=selected * (self.render_as == 'single_select'),
       
   400         is_checkboxes=selected * (self.render_as == 'multi_checkbox'),
       
   401         is_radio_buttons=selected * (self.render_as == 'quant_radio'),
       
   402         )
       
   403 
       
   404     str_value = forms.util.smart_unicode(value) # normalize to string.
       
   405     chained_choices = enumerate(chain(self.choices, choices))
       
   406     choices = {}
       
   407 
       
   408     for i, (option_value, option_label) in chained_choices:
       
   409       option_value = escape(forms.util.smart_unicode(option_value))
       
   410       choices[i] = option_value
       
   411     context['choices'] = choices
       
   412 
       
   413     template = 'soc/survey/universal_choice_editor.html'
       
   414     return loader.render_to_string(template, context)
       
   415 
       
   416 
       
   417 class PickOneField(forms.ChoiceField):
       
   418   """Stub for customizing the single choice field.
       
   419   """
       
   420   #TODO(james): Ensure that more than one option cannot be selected
       
   421 
       
   422   def __init__(self, *args, **kwargs):
       
   423     super(PickOneField, self).__init__(*args, **kwargs)
       
   424 
       
   425 
       
   426 class PickManyField(forms.MultipleChoiceField):
       
   427   """Stub for customizing the multiple choice field.
       
   428   """
       
   429 
       
   430   def __init__(self, *args, **kwargs):
       
   431     super(PickManyField, self).__init__(*args, **kwargs)
       
   432 
       
   433 
       
   434 class PickQuantField(forms.MultipleChoiceField):
       
   435   """Stub for customizing the multiple choice field.
       
   436   """
       
   437   #TODO(james): Ensure that more than one quant cannot be selected
       
   438 
       
   439   def __init__(self, *args, **kwargs):
       
   440     super(PickQuantField, self).__init__(*args, **kwargs)
       
   441 
       
   442 
       
   443 class PickOneSelect(forms.Select):
       
   444   """Stub for customizing the single choice select widget.
       
   445   """
       
   446 
       
   447   def __init__(self, *args, **kwargs):
       
   448     super(PickOneSelect, self).__init__(*args, **kwargs)
       
   449 
       
   450 
       
   451 class PickManyCheckbox(forms.CheckboxSelectMultiple):
       
   452   """Customized multiple choice checkbox widget.
       
   453   """
       
   454 
       
   455   def __init__(self, *args, **kwargs):
       
   456     super(PickManyCheckbox, self).__init__(*args, **kwargs)
       
   457 
       
   458   def render(self, name, value, attrs=None, choices=()):
       
   459     """Render checkboxes as list items grouped in a fieldset.
       
   460 
       
   461     This is the pick_multi widget for survey taking
       
   462     """
       
   463 
       
   464     if value is None:
       
   465       value = []
       
   466     has_id = attrs and attrs.has_key('id')
       
   467     final_attrs = self.build_attrs(attrs, name=name)
       
   468 
       
   469     # normalize to strings.
       
   470     str_values = set([forms.util.smart_unicode(v) for v in value])
       
   471     is_checked = lambda value: value in str_values
       
   472     smart_unicode = forms.util.smart_unicode
       
   473 
       
   474     # set container fieldset and list
       
   475     output = [u'<fieldset id="id_%s">\n  <ul class="pick_multi">' % name]
       
   476 
       
   477     # add numbered checkboxes wrapped in list items
       
   478     chained_choices = enumerate(chain(self.choices, choices))
       
   479     for i, (option_value, option_label) in chained_choices:
       
   480       option_label = escape(smart_unicode(option_label))
       
   481 
       
   482       # If an ID attribute was given, add a numeric index as a suffix,
       
   483       # so that the checkboxes don't all have the same ID attribute.
       
   484       if has_id:
       
   485         final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
       
   486 
       
   487       cb = widgets.CheckboxInput(final_attrs, check_test=is_checked)
       
   488       rendered_cb = cb.render(name, option_value)
       
   489       cb_label = (rendered_cb, option_label)
       
   490 
       
   491       output.append(u'    <li><label>%s %s</label></li>' % cb_label)
       
   492 
       
   493     output.append(u'  </ul>\n</fieldset>')
       
   494     return u'\n'.join(output)
       
   495 
       
   496   def id_for_label(self, id_):
       
   497     # see the comment for RadioSelect.id_for_label()
       
   498     if id_:
       
   499       id_ += '_fieldset'
       
   500     return id_
       
   501   id_for_label = classmethod(id_for_label)
       
   502 
       
   503 
       
   504 class PickQuantRadioRenderer(widgets.RadioFieldRenderer):
       
   505   """Used by PickQuantRadio to enable customization of radio widgets.
       
   506   """
       
   507 
       
   508   def __init__(self, *args, **kwargs):
       
   509     super(PickQuantRadioRenderer, self).__init__(*args, **kwargs)
       
   510 
       
   511   def render(self):
       
   512     """Outputs set of radio fields in a div.
       
   513     """
       
   514 
       
   515     return mark_safe(u'<div class="quant_radio">\n%s\n</div>'
       
   516                      % u'\n'.join([u'%s' % force_unicode(w) for w in self]))
       
   517 
       
   518 
       
   519 class PickQuantRadio(forms.RadioSelect):
       
   520   """TODO(James,Ajaksu) Fix Docstring
       
   521   """
       
   522 
       
   523   renderer = PickQuantRadioRenderer
       
   524 
       
   525   def __init__(self, *args, **kwargs):
       
   526     super(PickQuantRadio, self).__init__(*args, **kwargs)
       
   527 
       
   528 
       
   529 # in the future, we'll have more widget types here
       
   530 WIDGETS = {'multi_checkbox': PickManyCheckbox,
       
   531            'single_select': PickOneSelect,
       
   532            'quant_radio': PickQuantRadio}
       
   533 
       
   534 
       
   535 class SurveyResults(widgets.Widget):
       
   536   """Render List of Survey Results For Given Survey.
       
   537   """
       
   538 
       
   539   def render(self, survey, params, filter=filter, limit=1000, offset=0,
       
   540              order=[], idx=0, context={}):
       
   541     """ renders list of survey results
       
   542 
       
   543     params:
       
   544       survey: current survey
       
   545       params: dict of params for rendering list
       
   546       filter: filter for list results
       
   547       limit: limit for list results
       
   548       offset: offset for list results
       
   549       order: order for list results
       
   550       idx: index for list results
       
   551       context: context dict for template
       
   552     """
       
   553 
       
   554     logic = results_logic
       
   555     filter = {'survey': survey}
       
   556     data = logic.getForFields(filter=filter, limit=limit, offset=offset,
       
   557                               order=order)
       
   558 
       
   559     params['name'] = "Survey Results"
       
   560     content = {
       
   561       'idx': idx,
       
   562       'data': data,
       
   563       'logic': logic,
       
   564       'limit': limit,
       
   565      }
       
   566     updates = dicts.rename(params, params['list_params'])
       
   567     content.update(updates)
       
   568     contents = [content]
       
   569 
       
   570     if len(content) == 1:
       
   571       content = content[0]
       
   572       key_order = content.get('key_order')
       
   573 
       
   574     context['list'] = Lists(contents)
       
   575 
       
   576     # TODO(ajaksu) is this the best way to build the results list?
       
   577     for list_ in context['list']._contents:
       
   578       if len(list_['data']) < 1:
       
   579         return "<p>No Survey Results Have Been Submitted</p>"
       
   580 
       
   581       list_['row'] = 'soc/survey/list/results_row.html'
       
   582       list_['heading'] = 'soc/survey/list/results_heading.html'
       
   583       list_['description'] = 'Survey Results:'
       
   584 
       
   585     context['properties'] = survey.survey_content.orderedProperties()
       
   586     context['entity_type'] = "Survey Results"
       
   587     context['entity_type_plural'] = "Results"
       
   588     context['no_lists_msg'] = "No Survey Results"
       
   589 
       
   590     path = (survey.entity_type().lower(), survey.prefix,
       
   591             survey.scope_path, survey.link_id)
       
   592     context['grade_action'] = "/%s/grade/%s/%s/%s" % path
       
   593 
       
   594     markup = loader.render_to_string('soc/survey/results.html',
       
   595                                      dictionary=context).strip('\n')
       
   596     return markup
       
   597 
       
   598 
       
   599 def getRoleSpecificFields(survey, user, this_project, survey_form,
       
   600                           survey_record):
       
   601   """For evaluations, mentors get required Project and Grade fields, and
       
   602   students get a required Project field. 
       
   603 
       
   604   Because we need to get a list of the user's projects, we call the 
       
   605   logic getProjects method, which doubles as an access check.
       
   606   (No projects means that the survey cannot be taken.)
       
   607 
       
   608   params:
       
   609     survey: the survey being taken
       
   610     user: the survey-taking user
       
   611     this_project: either an already-selected project, or None
       
   612     survey_form: the surveyForm widget for this survey
       
   613     survey_record: an existing survey record for a user-project-survey combo,
       
   614       or None
       
   615   """
       
   616 
       
   617   from django import forms
       
   618 
       
   619   field_count = len(eval(survey.survey_content.schema).items())
       
   620   these_projects = survey_logic.getProjects(survey, user)
       
   621   if not these_projects: return False # no projects found
       
   622 
       
   623   project_pairs = []
       
   624   #insert a select field with options for each project
       
   625   for project in these_projects:
       
   626     project_pairs.append((project.key(), project.title))
       
   627   if project_pairs:
       
   628     project_tuples = tuple(project_pairs)
       
   629     # add select field containing list of projects
       
   630     projectField =  forms.fields.ChoiceField(
       
   631                               choices=project_tuples,
       
   632                               required=True,
       
   633                               widget=forms.Select())
       
   634     projectField.choices.insert(0, (None, "Choose a Project")  )
       
   635     # if editing an existing survey
       
   636     if not this_project and survey_record:
       
   637       this_project = survey_record.project
       
   638     if this_project:
       
   639       for tup in project_tuples:
       
   640         if tup[1] == this_project.title:
       
   641           if survey_record: project_name = tup[1] + " (Saved)"
       
   642           else: project_name = tup[1]
       
   643           projectField.choices.remove(tup)
       
   644           projectField.choices.insert(0, (tup[0], project_name)  )
       
   645           break
       
   646     survey_form.fields.insert(0, 'project', projectField )
       
   647 
       
   648   if survey.taking_access == "mentor evaluation":
       
   649     # If this is a mentor, add a field
       
   650     # determining if student passes or fails.
       
   651     # Activate grades handler should determine whether new status
       
   652     # is midterm_passed, final_passed, etc.
       
   653     grade_choices = (('pass', 'Pass'), ('fail', 'Fail'))
       
   654     grade_vals = { 'pass': True, 'fail': False }
       
   655     gradeField = forms.fields.ChoiceField(choices=grade_choices,
       
   656                                            required=True,
       
   657                                            widget=forms.Select())
       
   658 
       
   659     gradeField.choices.insert(0, (None, "Choose a Grade")  )
       
   660     if survey_record:
       
   661       for g in grade_choices:
       
   662         if grade_vals[g[0]] == survey_record.grade:
       
   663           gradeField.choices.insert(0, (g[0],g[1] + " (Saved)")   )
       
   664           gradeField.choices.remove(g)
       
   665           break;
       
   666       gradeField.show_hidden_initial = True
       
   667 
       
   668     survey_form.fields.insert(field_count + 1, 'grade', gradeField)
       
   669 
       
   670   return survey_form