app/soc/views/helper/surveys.py
changeset 2517 97117d341f62
parent 2507 4a6b8d6674b6
child 2518 66405056baf8
--- a/app/soc/views/helper/surveys.py	Fri Jul 03 20:38:43 2009 +0200
+++ b/app/soc/views/helper/surveys.py	Fri Jul 03 21:06:22 2009 +0200
@@ -46,6 +46,8 @@
 from soc.models.survey import SurveyContent
 
 
+CHOICE_TYPES = set(('selection', 'pick_multi', 'choice', 'pick_quant'))
+
 # TODO(ajaksu) add this to template
 REQUIRED_COMMENT_TPL = """
   <label for="required_for_{{ name }}">Required</label>
@@ -66,12 +68,10 @@
 """
 
 
-class SurveyForm(djangoforms.ModelForm):
-  """Main SurveyContent form.
+class SurveyTakeForm(djangoforms.ModelForm):
+  """SurveyContent form for recording survey answers.
 
-  This class is used to produce survey forms for several circumstances:
-    - Admin creating survey from scratch
-    - Admin updating existing survey
+  This class is used to produce survey forms for survey taking:
     - User taking survey
     - User updating already taken survey
 
@@ -82,19 +82,18 @@
   def __init__(self, *args, **kwargs):
     """Store special kwargs as attributes.
 
+      survey_content: a SuveryContent entity.
+      survey_logic: instance of SurveyLogic.
+      survey_record: a SurveyRecord entity.
       read_only: controls whether the survey taking UI allows data entry.
-      editing: controls whether to show the edit or show form.
     """
 
     self.kwargs = kwargs
+
     self.survey_content = self.kwargs.pop('survey_content', None)
-    self.this_user = self.kwargs.pop('this_user', None)
-    self.project = self.kwargs.pop('project', None)
     self.survey_logic = self.kwargs.pop('survey_logic', None)
     self.survey_record = self.kwargs.pop('survey_record', None)
-
     self.read_only = self.kwargs.pop('read_only', None)
-    self.editing = self.kwargs.pop('editing', None)
 
     self.fields_map = dict(
         long_answer=self.addLongField,
@@ -105,7 +104,7 @@
         )
 
     self.kwargs['data'] = {}
-    super(SurveyForm, self).__init__(*args, **self.kwargs)
+    super(SurveyTakeForm, self).__init__(*args, **self.kwargs)
 
   def getFields(self, post_dict=None):
     """Build the SurveyContent (questions) form fields.
@@ -123,21 +122,19 @@
     post_dict = post_dict or {}
     self.survey_fields = {}
     schema = SurveyContentSchema(self.survey_content.schema)
-    has_record = (not self.editing) and (self.survey_record or post_dict)
+    has_record = self.survey_record or post_dict
     extra_attrs = {}
 
     # figure out whether we want a read-only view
-    if not self.editing:
-      # only survey taking can be read-only
-      read_only = self.read_only
+    read_only = self.read_only
 
-      if not read_only:
-        survey_content = self.survey_content
-        survey_entity = self.survey_logic.getSurveyForContent(survey_content)
-        deadline = survey_entity.survey_end
-        read_only =  deadline and (datetime.datetime.now() > deadline)
-      else:
-        extra_attrs['disabled'] = 'disabled'
+    if not read_only:
+      survey_content = self.survey_content
+      survey_entity = self.survey_logic.getSurveyForContent(survey_content)
+      deadline = survey_entity.survey_end
+      read_only =  deadline and (datetime.datetime.now() > deadline)
+    else:
+      extra_attrs['disabled'] = 'disabled'
 
     # flag whether we can use getlist to retrieve multiple values
     is_post = hasattr(post_dict, 'getlist')
@@ -182,14 +179,14 @@
         value = []
 
       # record field value for validation
-      if not from_content:
-        self.data[field] = value
+      #if not from_content:
+      self.data[field] = value
 
       # find correct field type
       addField = self.fields_map[schema.getType(field)]
 
       # check if question is required, it's never required when editing
-      required = not self.editing and schema.getRequired(field)
+      required = schema.getRequired(field)
       kwargs = dict(label=label, req=required)
 
       # add new field
@@ -212,12 +209,12 @@
     for position, property in survey_order.items():
       position = position * 2
       self.fields.insert(position, property, self.survey_fields[property])
-      if not self.editing:
-        # add comment if field has one and this isn't an edit view
-        property = COMMENT_PREFIX + property
-        if property in self.survey_fields:
-          self.fields.insert(position - 1, property,
-                             self.survey_fields[property])
+
+      # add comment if field has one and this isn't an edit view
+      property = COMMENT_PREFIX + property
+      if property in self.survey_fields:
+        self.fields.insert(position - 1, property,
+                           self.survey_fields[property])
     return self.fields
 
   def addLongField(self, field, value, attrs, schema, req=True, label='',
@@ -235,11 +232,7 @@
       comment: initial comment value for field
     """
 
-    # use a widget that allows setting required and comments
-    has_comment = schema.getHasComment(field)
-    is_required = schema.getRequired(field)
-    widget = LongTextarea(is_required, has_comment, attrs=attrs,
-                          editing=self.editing)
+    widget = widgets.Textarea(attrs=attrs)
 
     if not tip:
       tip = 'Please provide a long answer to this question.'
@@ -266,11 +259,7 @@
 
     attrs['class'] = "text_question"
 
-    # use a widget that allows setting required and comments
-    has_comment = schema.getHasComment(field)
-    is_required = schema.getRequired(field)
-    widget = ShortTextInput(is_required, has_comment, attrs=attrs,
-                          editing=self.editing)
+    widget = widgets.TextInput(attrs=attrs)
 
     if not tip:
       tip = 'Please provide a short answer to this question.'
@@ -295,10 +284,11 @@
       comment: initial comment value for field
     """
 
-    widget = schema.getWidget(field, self.editing, attrs)
+    widget = PickOneSelect(attrs)
 
     these_choices = []
     # add all properties, but select chosen one
+    # TODO(ajaksu): this breaks ordering and blocks merging choice methods
     options = getattr(self.survey_content, field)
     has_record = not self.editing and self.survey_record
     if has_record and hasattr(self.survey_record, field):
@@ -332,7 +322,7 @@
 
     """
 
-    widget = schema.getWidget(field, self.editing, attrs)
+    widget = PickManyCheckbox(attrs)
 
     # TODO(ajaksu) need to allow checking checkboxes by default
     if self.survey_record and isinstance(value, basestring):
@@ -365,7 +355,7 @@
 
     """
 
-    widget = schema.getWidget(field, self.editing, attrs)
+    widget = PickQuantRadio(attrs)
 
     if self.survey_record:
       value = value
@@ -382,11 +372,92 @@
     self.survey_fields[field] = question
 
   def addCommentField(self, field, comment, attrs, tip):
-    if not self.editing:
-      widget = widgets.Textarea(attrs=attrs)
-      comment_field = CharField(help_text=tip, required=False, label='Comments',
-                          widget=widget, initial=comment)
-      self.survey_fields[COMMENT_PREFIX + field] = comment_field
+    widget = widgets.Textarea(attrs=attrs)
+    comment_field = CharField(help_text=tip, required=False, label='Comments',
+                        widget=widget, initial=comment)
+    self.survey_fields[COMMENT_PREFIX + field] = comment_field
+
+
+  class Meta(object):
+    model = SurveyContent
+    exclude = ['schema']
+
+
+class SurveyEditForm(djangoforms.ModelForm):
+  """SurveyContent form for editing a survey.
+
+  This class is used to produce survey forms for several circumstances:
+    - Admin creating survey from scratch
+    - Admin updating existing survey
+
+  Using dynamic properties of the survey model (if passed as an arg) the
+  survey form is dynamically formed.
+  """
+
+  def __init__(self, *args, **kwargs):
+    """Store special kwargs as attributes.
+
+      survey_content: a SurveyContent entity.
+      survey_logic: an instance of SurveyLogic.
+    """
+
+    self.kwargs = kwargs
+    self.survey_content = self.kwargs.pop('survey_content', None)
+    self.survey_logic = self.kwargs.pop('survey_logic', None)
+
+    super(SurveyEditForm, self).__init__(*args, **self.kwargs)
+
+  def getFields(self):
+    """Build the SurveyContent (questions) form fields.
+
+    params:
+      post_dict: dict with POST data that will be used for validation
+
+    Populates self.survey_fields, which will be ordered in self.insert_fields.
+    Also populates self.data, which will be used in form validation.
+    """
+
+    if not self.survey_content:
+      return
+
+    self.survey_fields = {}
+    schema = SurveyContentSchema(self.survey_content.schema)
+    extra_attrs = {}
+
+    # add unordered fields to self.survey_fields
+    for field in self.survey_content.dynamic_properties():
+
+      # use prompts set by survey creator
+      value = getattr(self.survey_content, field)
+      from_content = True
+
+      label = schema.getLabel(field)
+      if label is None:
+        continue
+
+      tip = 'Please provide an answer to this question.'
+      kwargs = schema.getEditFieldArgs(field, value, tip, label)
+
+      kwargs['widget'] = schema.getEditWidget(field, extra_attrs)
+
+
+      # add new field
+      self.survey_fields[field] = schema.getEditField(field)(**kwargs)
+
+    # TODO(ajaksu): find a new way to keep fields in order
+    return self.insertFields()
+
+  def insertFields(self):
+    """Add ordered fields to self.fields.
+    """
+
+    survey_order = self.survey_content.getSurveyOrder()
+
+    # insert dynamic survey fields
+    for position, property in survey_order.items():
+      self.fields.insert(position, property, self.survey_fields[property])
+
+    return self.fields
 
   class Meta(object):
     model = SurveyContent
@@ -398,39 +469,106 @@
   """
 
   def __init__(self, schema):
+    """Set the dictionary that this class encapsulates.
+
+    Args:
+      schema: schema as stored in SurveyConent entity
+    """
+
     self.schema = eval(schema)
 
   def getType(self, field):
+    """Fetch question type for field e.g. short_answer, pick_multi, etc.
+
+    Args:
+      field: name of the field to get the type from
+    """
+
     return self.schema[field]["type"]
 
   def getRequired(self, field):
     """Check whether survey question is required.
+
+    Args:
+      field: name of the field to check the required property for
     """
 
     return self.schema[field]["required"]
 
   def getHasComment(self, field):
     """Check whether survey question allows adding a comment.
+
+    Args:
+      field: name of the field to get the hasComment property for
     """
 
     return self.schema[field]["has_comment"]
 
   def getRender(self, field):
+    """Get rendering options for choice questions.
+
+    Args:
+      field: name of the field to get the rendering option for
+    """
+
     return self.schema[field]["render"]
 
-  def getWidget(self, field, editing, attrs):
-    """Get survey editing or taking widget for choice questions.
+  def getEditField(self, field):
+    """For a given question kind, get the correct edit view field.
+    """
+
+    kind = self.getType(field)
+    if kind in CHOICE_TYPES:
+      Field = PickOneField
+    else:
+      Field = CharField
+
+    return Field
+
+  def getEditFieldArgs(self, field, value, tip, label):
+    """Build edit view field arguments.
+
+    params:
+      field: field name
+      value: field value (text for text questions, list for choice questions)
+      tipe: help text, to be used in a tooltip
+      label: the field's question (or identifier if question is missing)
     """
 
-    if editing:
-      kind = self.getType(field)
+    kind = self.getType(field)
+
+    kwargs = dict(help_text=tip, required=False, label=label)
+
+    if kind in CHOICE_TYPES:
+      kwargs['choices'] = tuple([(val, val) for val in value])
+    else:
+      kwargs['initial'] = value
+
+    return kwargs
+
+  def getEditWidget(self, field, attrs):
+    """Get survey editing widget for questions.
+    """
+
+    kind = self.getType(field)
+    is_required = self.getRequired(field)
+    has_comment = self.getHasComment(field)
+
+    if kind in CHOICE_TYPES:
+      widget = UniversalChoiceEditor
       render = self.getRender(field)
-      is_required = self.getRequired(field)
-      has_comment = self.getHasComment(field)
-      widget = UniversalChoiceEditor(kind, render, is_required, has_comment)
+      args = kind, render, is_required, has_comment
     else:
-      widget = WIDGETS[self.schema[field]['render']](attrs=attrs)
-    return widget
+      args = is_required, has_comment
+      if kind == 'long_answer':
+        attrs['class'] = "text_question"
+        widget = LongTextarea
+      elif kind == 'short_answer':
+        widget = ShortTextInput
+
+    kwargs = dict(attrs=attrs)
+
+    return widget(*args, **kwargs)
 
   def getLabel(self, field):
     """Fetch the free text 'question' or use field name as label.
@@ -547,7 +685,7 @@
   """Set whether long question is required or allows comments.
   """
 
-  def __init__(self, is_required, has_comment, attrs=None, editing=False):
+  def __init__(self, is_required, has_comment, attrs=None):
     """Initialize widget and store editing mode.
 
     params:
@@ -556,7 +694,6 @@
       editing: bool, controls rendering as plain textarea or with extra fields
     """
 
-    self.editing = editing
     self.is_required = is_required
     self.has_comment = has_comment
 
@@ -571,15 +708,14 @@
     # plain text area
     output = super(LongTextarea, self).render(name, value, attrs)
 
-    if self.editing:
-      # add 'required' and 'has_comment' fields
-      context = dict(name=name, is_required=self.is_required,
-                     has_comment=self.has_comment)
-      template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
-      rendered = template.render(context=loader.Context(dict_=context))
-      output =  rendered + output
+    # add 'required' and 'has_comment' fields
+    context = dict(name=name, is_required=self.is_required,
+                   has_comment=self.has_comment)
+    template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
+    rendered = template.render(context=loader.Context(dict_=context))
+    output =  rendered + output
 
-      output = '<fieldset>' + output + '</fieldset>'
+    output = '<fieldset>' + output + '</fieldset>'
     return output
 
 
@@ -587,7 +723,7 @@
   """Set whether short answer question is required or allows comments.
   """
 
-  def __init__(self, is_required, has_comment, attrs=None, editing=False):
+  def __init__(self, is_required, has_comment, attrs=None):
     """Initialize widget and store editing mode.
 
     params:
@@ -596,7 +732,6 @@
       editing: bool, controls rendering as plain text input or with extra fields
     """
 
-    self.editing = editing
     self.is_required = is_required
     self.has_comment = has_comment
 
@@ -611,15 +746,14 @@
     # plain text area
     output = super(ShortTextInput, self).render(name, value, attrs)
 
-    if self.editing:
-      # add 'required' and 'has_comment' fields
-      context = dict(name=name, is_required=self.is_required,
-                     has_comment=self.has_comment)
-      template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
-      rendered = template.render(context=loader.Context(dict_=context))
-      output =  rendered + output
+    # add 'required' and 'has_comment' fields
+    context = dict(name=name, is_required=self.is_required,
+                   has_comment=self.has_comment)
+    template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
+    rendered = template.render(context=loader.Context(dict_=context))
+    output =  rendered + output
 
-      output = '<fieldset>' + output + '</fieldset>'
+    output = '<fieldset>' + output + '</fieldset>'
     return output
 
 
@@ -780,79 +914,6 @@
     return markup
 
 
-def getRoleSpecificFields(survey, user, this_project, survey_form,
-                          survey_record):
-  """For evaluations, mentors get required Project and Grade fields, and
-  students get a required Project field.
-
-  Because we need to get a list of the user's projects, we call the
-  logic getProjects method, which doubles as an access check.
-  (No projects means that the survey cannot be taken.)
-
-  params:
-    survey: the survey being taken
-    user: the survey-taking user
-    this_project: either an already-selected project, or None
-    survey_form: the surveyForm widget for this survey
-    survey_record: an existing survey record for a user-project-survey combo,
-      or None
-  """
-
-  field_count = len(eval(survey.survey_content.schema).items())
-  these_projects = survey_logic.getProjects(survey, user)
-  if not these_projects:
-    return False # no projects found
-
-  project_pairs = []
-  #insert a select field with options for each project
-  for project in these_projects:
-    project_pairs.append((project.key(), project.title))
-  if project_pairs:
-    project_tuples = tuple(project_pairs)
-    # add select field containing list of projects
-    projectField =  forms.fields.ChoiceField(
-                              choices=project_tuples,
-                              required=True,
-                              widget=forms.Select())
-    projectField.choices.insert(0, (None, "Choose a Project")  )
-    # if editing an existing survey
-    if not this_project and survey_record:
-      this_project = survey_record.project
-    if this_project:
-      for tup in project_tuples:
-        if tup[1] == this_project.title:
-          if survey_record: project_name = tup[1] + " (Saved)"
-          else: project_name = tup[1]
-          projectField.choices.remove(tup)
-          projectField.choices.insert(0, (tup[0], project_name)  )
-          break
-    survey_form.fields.insert(0, 'project', projectField )
-
-  if survey.taking_access == "mentor evaluation":
-    # If this is a mentor, add a field
-    # determining if student passes or fails.
-    # Activate grades handler should determine whether new status
-    # is midterm_passed, final_passed, etc.
-    grade_choices = (('pass', 'Pass'), ('fail', 'Fail'))
-    grade_vals = { 'pass': True, 'fail': False }
-    gradeField = forms.fields.ChoiceField(choices=grade_choices,
-                                           required=True,
-                                           widget=forms.Select())
-
-    gradeField.choices.insert(0, (None, "Choose a Grade")  )
-    if survey_record:
-      for g in grade_choices:
-        if grade_vals[g[0]] == survey_record.grade:
-          gradeField.choices.insert(0, (g[0],g[1] + " (Saved)")   )
-          gradeField.choices.remove(g)
-          break;
-      gradeField.show_hidden_initial = True
-
-    survey_form.fields.insert(field_count + 1, 'grade', gradeField)
-
-  return survey_form
-
-
 class HelperForm(object):
   """Thin wrapper for adding values to params['edit_form'].fields.
   """