Added Survey From Field validation.
authorDaniel Diniz <ajaksu@gmail.com>
Fri, 03 Jul 2009 14:19:23 +0200
changeset 2501 d612b48e6e12
parent 2500 ecc16ffe174b
child 2502 2e096acc8720
Added Survey From Field validation. Also added the COMMENT_PREFIX in Survey model to ensure that comments are stored in the same way everwhere. Reviewed by: Lennard de Rijk
app/soc/models/survey.py
app/soc/views/helper/surveys.py
app/soc/views/models/survey.py
--- a/app/soc/models/survey.py	Fri Jul 03 12:10:24 2009 +0200
+++ b/app/soc/models/survey.py	Fri Jul 03 14:19:23 2009 +0200
@@ -34,6 +34,9 @@
 import soc.models.work
 
 
+COMMENT_PREFIX = 'comment_for_'
+
+
 class SurveyContent(db.Expando):
   """Fields (questions) and schema representation of a Survey.
 
@@ -93,7 +96,7 @@
   SURVEY_ACCESS = ['admin', 'restricted', 'member', 'user']
 
   # these are GSoC specific, so eventually we can subclass this
-  SURVEY_TAKING_ACCESS = ['student', 
+  SURVEY_TAKING_ACCESS = ['student',
                           'mentor',
                           'org_admin',
                           'user',
--- a/app/soc/views/helper/surveys.py	Fri Jul 03 12:10:24 2009 +0200
+++ b/app/soc/views/helper/surveys.py	Fri Jul 03 14:19:23 2009 +0200
@@ -42,6 +42,7 @@
 
 from soc.logic import dicts
 from soc.logic.lists import Lists
+from soc.models.survey import COMMENT_PREFIX
 from soc.models.survey import SurveyContent
 
 
@@ -83,20 +84,26 @@
         pick_quant=self.addQuantField,
         )
 
+    self.kwargs['data'] = {}
     super(SurveyForm, self).__init__(*args, **self.kwargs)
 
-  def getFields(self):
+  def getFields(self, post_dict=None):
     """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
 
+    post_dict = post_dict or {}
     self.survey_fields = {}
     schema = SurveyContentSchema(self.survey_content.schema)
-    has_record = (not self.editing) and self.survey_record
+    has_record = (not self.editing) and (self.survey_record or post_dict)
     extra_attrs = {}
 
     # figure out whether we want a read-only view
@@ -117,23 +124,51 @@
 
       # a comment made by the user
       comment = ''
-      if has_record and hasattr(self.survey_record, field):
+
+      # flag to know where the value came from
+      from_content = False
+
+      if has_record and field in post_dict:
+        # entered value that is not yet saved
+        value = post_dict[field]
+        if COMMENT_PREFIX + field in post_dict:
+          comment = post_dict[COMMENT_PREFIX + field]
+      elif has_record and hasattr(self.survey_record, field):
         # previously entered value
         value = getattr(self.survey_record, field)
-        if hasattr(self.survey_record, 'comment_for_' + field):
-          comment = getattr(self.survey_record, 'comment_for_' + field)
+        if hasattr(self.survey_record, COMMENT_PREFIX + field):
+          comment = getattr(self.survey_record, COMMENT_PREFIX + field)
       else:
         # use prompts set by survey creator
         value = getattr(self.survey_content, field)
+        from_content = True
 
       label = schema.getLabel(field)
       if label is None:
         continue
 
-      # dispatch to field-specific methods
+      # fix validation for pick_multi fields
+      is_multi = schema.getType(field) == 'pick_multi'
+      if not from_content and schema.getType(field) == 'pick_multi':
+        if isinstance(value, basestring):
+          value = value.split(',')
+      elif from_content and is_multi:
+        value = []
+
+      # record field value for validation
+      if not from_content:
+        self.data[field] = value
+
+      # find correct field type
       addField = self.fields_map[schema.getType(field)]
       addField(field, value, extra_attrs, schema, label=label, comment=comment)
 
+      # handle comments
+      comment_name = COMMENT_PREFIX + field
+      if comment_name in post_dict or hasattr(self.survey_record, comment_name):
+        self.data[comment_name] = comment
+        self.addCommentField(field, comment, extra_attrs, tip='Add a comment.')
+
     return self.insertFields()
 
   def insertFields(self):
@@ -147,9 +182,11 @@
       position = position * 2
       self.fields.insert(position, property, self.survey_fields[property])
       if not self.editing:
-        property = 'comment_for_' + property
-        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=False, label='',
@@ -176,7 +213,6 @@
                          widget=widget, initial=value)
 
     self.survey_fields[field] = question
-    self.addCommentField(field, comment, attrs, tip)
 
   def addShortField(self, field, value, attrs, schema, req=False, label='',
                     tip='', comment=''):
@@ -203,7 +239,6 @@
                          widget=widget, max_length=140, initial=value)
 
     self.survey_fields[field] = question
-    self.addCommentField(field, comment, attrs, tip)
 
   def addSingleField(self, field, value, attrs, schema, req=False, label='',
                      tip='', comment=''):
@@ -240,7 +275,6 @@
                             choices=tuple(these_choices), widget=widget)
 
     self.survey_fields[field] = question
-    self.addCommentField(field, comment, attrs, tip)
 
   def addMultiField(self, field, value, attrs, schema, req=False, label='',
                     tip='', comment=''):
@@ -276,7 +310,6 @@
                              initial=value)
 
     self.survey_fields[field] = question
-    self.addCommentField(field, comment, attrs, tip)
 
   def addQuantField(self, field, value, attrs, schema, req=False, label='',
                     tip='', comment=''):
@@ -309,14 +342,13 @@
                              choices=tuple(these_choices), widget=widget,
                              initial=value)
     self.survey_fields[field] = question
-    self.addCommentField(field, comment, attrs, tip)
 
   def addCommentField(self, field, comment, attrs, tip):
     if not self.editing:
       widget = widgets.Textarea(attrs=attrs)
-      comment = CharField(help_text=tip, required=False, label='Comments',
+      comment_field = CharField(help_text=tip, required=False, label='Comments',
                           widget=widget, initial=comment)
-      self.survey_fields['comment_for_' + field] = comment
+      self.survey_fields[COMMENT_PREFIX + field] = comment_field
 
   class Meta(object):
     model = SurveyContent
@@ -606,10 +638,11 @@
       post_dict: dictionary with data from the POST request
   """
 
-  # TODO(ljvderijk) deal with the comment fields
+  # TODO(ljvderijk) deal with the comment fields neatly
 
   # get the schema for this survey
-  schema = eval(survey.survey_content.schema)
+  schema = SurveyContentSchema(survey.survey_content.schema)
+  schema_dict = schema.schema
 
   # fill a dictionary with the data to be stored in the SurveyRecord
   response_dict = {}
@@ -617,16 +650,25 @@
   for name, value in post_dict.items():
     # make sure name is a string
     name = name.encode()
-    if name not in schema:
+
+    if name not in schema_dict:
       # property not in survey schema ignore
       continue
     else:
-      pick_multi = schema[name]['type'] == 'pick_multi'
+      pick_multi = schema.getType(name) == 'pick_multi'
 
       if pick_multi and hasattr(post_dict, 'getlist'): # it's a multidict
-        response_dict[name] = ','.join(post_dict.getlist(name))
-      else:
-        response_dict[name] = value
+        # validation asks for a list of values
+        value = post_dict.getlist(name)
+
+    response_dict[name] = value
+
+    # handle comments
+    comment_name = COMMENT_PREFIX + name
+    if comment_name in post_dict:
+      comment = post_dict.get(comment_name)
+      if comment:
+        response_dict[comment_name] = comment
 
   return response_dict
 
@@ -796,16 +838,16 @@
     except StopIteration:
       # bail out early if survey_records.run() is empty
       return header, survey.link_id
-  
+
     # generate results list
     recs = record_query.run()
     recs = _get_records(recs, properties)
-  
+
     # write results to CSV
     output = StringIO.StringIO()
     writer = csv.writer(output)
     writer.writerow(properties)
     writer.writerows(recs)
-  
+
     return header + output.getvalue(), survey.link_id
   return wrapper
--- a/app/soc/views/models/survey.py	Fri Jul 03 12:10:24 2009 +0200
+++ b/app/soc/views/models/survey.py	Fri Jul 03 14:19:23 2009 +0200
@@ -43,6 +43,7 @@
 from soc.views import out_of_band
 from soc.views.helper import access
 from soc.views.helper import decorators
+from soc.views.helper import forms as forms_helper
 from soc.views.helper import redirects
 from soc.views.helper import responses
 from soc.views.helper import surveys
@@ -523,13 +524,14 @@
     return record_logic.getForFields(filter, unique=True)
 
   def takeGet(self, request, template, context, params, entity, record,
-              **kwargs):
+              form_data=None, **kwargs):
     """Handles the GET request for the Survey's take page.
 
     Args:
         template: the template used for this view
         entity: the Survey entity
         record: a SurveyRecord entity
+        form_data: dict with form data that will be used for validation
         rest: see base.View.public()
     """
 
@@ -537,7 +539,15 @@
                                      survey_record=record,
                                      survey_logic=self._params['logic'])
 
-    survey_form.getFields()
+    # fetch field contents and pass request data, if any
+    survey_form.getFields(post_dict=form_data)
+
+    # validate request data
+    if form_data and not survey_form.is_valid():
+      survey_form.full_clean()
+      context['survey_form'] = survey_form
+      return self._constructResponse(request, entity=None, context=context,
+          form=survey_form, params=params, template=template)
 
     # fill context with the survey and additional information
     context['survey_form'] = survey_form
@@ -576,8 +586,20 @@
     survey_logic = params['logic']
     record_logic = survey_logic.getRecordLogic()
 
-    # TODO: check the validity of the form data
-    properties = surveys.getSurveyResponseFromPost(entity, request.POST)
+    # create a form to validate
+    survey_form = surveys.SurveyForm(survey_content=entity.survey_content,
+                                     survey_record=None,
+                                     survey_logic=self._params['logic'])
+    # fill form with request data
+    survey_form.getFields(post_dict=request.POST)
+
+    if not survey_form.is_valid():
+      # redirect to takeGet so we can handle errors
+      return self.takeGet(request, template, context, params, entity, record,
+                          form_data=properties)
+
+    # retrieve the data from the form
+    _, properties = forms_helper.collectCleanedFields(survey_form)
 
     # add the required SurveyRecord properties
     properties['user'] = user_logic.getForCurrentAccount()
@@ -614,7 +636,7 @@
     Args:
         context: the context for the view to update
         survey: a Survey entity
-        survey_record: a SurveyRecordEntity 
+        survey_record: a SurveyRecordEntity
     """
 
     if not survey.survey_end: