app/soc/views/helper/surveys.py
changeset 2502 2e096acc8720
parent 2501 d612b48e6e12
child 2506 c98bf890156e
equal deleted inserted replaced
2501:d612b48e6e12 2502:2e096acc8720
    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 # TODO(ajaksu) add this to template
       
    50 REQUIRED_COMMENT_TPL = """
       
    51   <label for="required_for_{{ name }}">Required</label>
       
    52   <select id="required_for_{{ name }}" name="required_for_{{ name }}">
       
    53     <option value="True" {% if is_required %} selected='selected' {% endif %}
       
    54      >True</option>
       
    55     <option value="False" {% if not is_required %} selected='selected'
       
    56      {% endif %}>False</option>
       
    57   </select><br/>
       
    58 
       
    59   <label for="comment_for_{{ name }}">Allow Comments</label>
       
    60   <select id="comment_for_{{ name }}" name="comment_for_{{ name }}">
       
    61     <option value="True" {% if has_comment %} selected='selected' {% endif %}
       
    62      >True</option>
       
    63     <option value="False" {% if not has_comment %} selected='selected'
       
    64      {% endif %}>False</option>
       
    65   </select><br/>
       
    66 """
       
    67 
       
    68 
    49 class SurveyForm(djangoforms.ModelForm):
    69 class SurveyForm(djangoforms.ModelForm):
    50   """Main SurveyContent form.
    70   """Main SurveyContent form.
    51 
    71 
    52   This class is used to produce survey forms for several circumstances:
    72   This class is used to produce survey forms for several circumstances:
    53     - Admin creating survey from scratch
    73     - Admin creating survey from scratch
   159       if not from_content:
   179       if not from_content:
   160         self.data[field] = value
   180         self.data[field] = value
   161 
   181 
   162       # find correct field type
   182       # find correct field type
   163       addField = self.fields_map[schema.getType(field)]
   183       addField = self.fields_map[schema.getType(field)]
   164       addField(field, value, extra_attrs, schema, label=label, comment=comment)
   184 
   165 
   185       # check if question is required, it's never required when editing
   166       # handle comments
   186       required = not self.editing and schema.getRequired(field)
   167       comment_name = COMMENT_PREFIX + field
   187       kwargs = dict(label=label, req=required)
   168       if comment_name in post_dict or hasattr(self.survey_record, comment_name):
   188 
   169         self.data[comment_name] = comment
   189       # add new field
       
   190       addField(field, value, extra_attrs, schema, **kwargs)
       
   191 
       
   192       # handle comments if question allows them
       
   193       if schema.getHasComment(field):
       
   194         self.data[COMMENT_PREFIX + field] = comment
   170         self.addCommentField(field, comment, extra_attrs, tip='Add a comment.')
   195         self.addCommentField(field, comment, extra_attrs, tip='Add a comment.')
   171 
   196 
   172     return self.insertFields()
   197     return self.insertFields()
   173 
   198 
   174   def insertFields(self):
   199   def insertFields(self):
   187         if property in self.survey_fields:
   212         if property in self.survey_fields:
   188           self.fields.insert(position - 1, property,
   213           self.fields.insert(position - 1, property,
   189                              self.survey_fields[property])
   214                              self.survey_fields[property])
   190     return self.fields
   215     return self.fields
   191 
   216 
   192   def addLongField(self, field, value, attrs, schema, req=False, label='',
   217   def addLongField(self, field, value, attrs, schema, req=True, label='',
   193                    tip='', comment=''):
   218                    tip='', comment=''):
   194     """Add a long answer fields to this form.
   219     """Add a long answer fields to this form.
   195 
   220 
   196     params:
   221     params:
   197       field: the current field
   222       field: the current field
   202       label: label for field
   227       label: label for field
   203       tip: tooltip text for field
   228       tip: tooltip text for field
   204       comment: initial comment value for field
   229       comment: initial comment value for field
   205     """
   230     """
   206 
   231 
   207     widget = widgets.Textarea(attrs=attrs)
   232     # use a widget that allows setting required and comments
       
   233     has_comment = schema.getHasComment(field)
       
   234     is_required = schema.getRequired(field)
       
   235     widget = LongTextarea(is_required, has_comment, attrs=attrs,
       
   236                           editing=self.editing)
   208 
   237 
   209     if not tip:
   238     if not tip:
   210       tip = 'Please provide a long answer to this question.'
   239       tip = 'Please provide a long answer to this question.'
   211 
   240 
   212     question = CharField(help_text=tip, required=req, label=label,
   241     question = CharField(help_text=tip, required=req, label=label,
   228       tip: tooltip text for field
   257       tip: tooltip text for field
   229       comment: initial comment value for field
   258       comment: initial comment value for field
   230     """
   259     """
   231 
   260 
   232     attrs['class'] = "text_question"
   261     attrs['class'] = "text_question"
   233     widget = widgets.TextInput(attrs=attrs)
   262 
       
   263     # use a widget that allows setting required and comments
       
   264     has_comment = schema.getHasComment(field)
       
   265     is_required = schema.getRequired(field)
       
   266     widget = ShortTextInput(is_required, has_comment, attrs=attrs,
       
   267                           editing=self.editing)
   234 
   268 
   235     if not tip:
   269     if not tip:
   236       tip = 'Please provide a short answer to this question.'
   270       tip = 'Please provide a short answer to this question.'
   237 
   271 
   238     question = CharField(help_text=tip, required=req, label=label,
   272     question = CharField(help_text=tip, required=req, label=label,
   296 
   330 
   297     # TODO(ajaksu) need to allow checking checkboxes by default
   331     # TODO(ajaksu) need to allow checking checkboxes by default
   298     if self.survey_record and isinstance(value, basestring):
   332     if self.survey_record and isinstance(value, basestring):
   299       # pass value as 'initial' so MultipleChoiceField renders checked boxes
   333       # pass value as 'initial' so MultipleChoiceField renders checked boxes
   300       value = value.split(',')
   334       value = value.split(',')
   301     else:
       
   302       value = None
       
   303 
   335 
   304     these_choices = [(v,v) for v in getattr(self.survey_content, field)]
   336     these_choices = [(v,v) for v in getattr(self.survey_content, field)]
   305     if not tip:
   337     if not tip:
   306       tip = 'Please select one or more of these choices.'
   338       tip = 'Please select one or more of these choices.'
   307 
   339 
   363     self.schema = eval(schema)
   395     self.schema = eval(schema)
   364 
   396 
   365   def getType(self, field):
   397   def getType(self, field):
   366     return self.schema[field]["type"]
   398     return self.schema[field]["type"]
   367 
   399 
       
   400   def getRequired(self, field):
       
   401     """Check whether survey question is required.
       
   402     """
       
   403 
       
   404     return self.schema[field]["required"]
       
   405 
       
   406   def getHasComment(self, field):
       
   407     """Check whether survey question allows adding a comment.
       
   408     """
       
   409 
       
   410     return self.schema[field]["has_comment"]
       
   411 
   368   def getRender(self, field):
   412   def getRender(self, field):
   369     return self.schema[field]["render"]
   413     return self.schema[field]["render"]
   370 
   414 
   371   def getWidget(self, field, editing, attrs):
   415   def getWidget(self, field, editing, attrs):
   372     """Get survey editing or taking widget for choice questions.
   416     """Get survey editing or taking widget for choice questions.
   373     """
   417     """
   374 
   418 
   375     if editing:
   419     if editing:
   376       kind = self.getType(field)
   420       kind = self.getType(field)
   377       render = self.getRender(field)
   421       render = self.getRender(field)
   378       widget = UniversalChoiceEditor(kind, render)
   422       is_required = self.getRequired(field)
       
   423       has_comment = self.getHasComment(field)
       
   424       widget = UniversalChoiceEditor(kind, render, is_required, has_comment)
   379     else:
   425     else:
   380       widget = WIDGETS[self.schema[field]['render']](attrs=attrs)
   426       widget = WIDGETS[self.schema[field]['render']](attrs=attrs)
   381     return widget
   427     return widget
   382 
   428 
   383   def getLabel(self, field):
   429   def getLabel(self, field):
   399   """Edit interface for choice questions.
   445   """Edit interface for choice questions.
   400 
   446 
   401   Allows adding and removing options, re-ordering and editing option text.
   447   Allows adding and removing options, re-ordering and editing option text.
   402   """
   448   """
   403 
   449 
   404   def __init__(self, kind, render, attrs=None, choices=()):
   450   def __init__(self, kind, render, is_required, has_comment, attrs=None,
       
   451                choices=()):
       
   452     """
       
   453     params:
       
   454       kind: question kind (one of selection, pick_multi or pick_quant)
       
   455       render: question widget (single_select, multi_checkbox or quant_radio)
       
   456       is_required: bool, controls selection in the required_for field
       
   457       has_comments: bool, controls selection in the has_comments field
       
   458     """
   405 
   459 
   406     self.attrs = attrs or {}
   460     self.attrs = attrs or {}
   407 
   461 
   408     # Choices can be any iterable, but we may need to render this widget
   462     # Choices can be any iterable, but we may need to render this widget
   409     # multiple times. Thus, collapse it into a list so it can be consumed
   463     # multiple times. Thus, collapse it into a list so it can be consumed
   410     # more than once.
   464     # more than once.
   411     self.choices = list(choices)
   465     self.choices = list(choices)
   412     self.kind = kind
   466     self.kind = kind
   413     self.render_as = render
   467     self.render_as = render
       
   468     self.is_required = is_required
       
   469     self.has_comment = has_comment
       
   470 
   414 
   471 
   415   def render(self, name, value, attrs=None, choices=()):
   472   def render(self, name, value, attrs=None, choices=()):
   416     """ renders UCE widget
   473     """Render UCE widget.
       
   474 
       
   475     Option reordering, editing, addition and deletion are added here.
   417     """
   476     """
   418 
   477 
   419     if value is None:
   478     if value is None:
   420       value = ''
   479       value = ''
   421 
   480 
   431         is_select=selected * (self.render_as == 'single_select'),
   490         is_select=selected * (self.render_as == 'single_select'),
   432         is_checkboxes=selected * (self.render_as == 'multi_checkbox'),
   491         is_checkboxes=selected * (self.render_as == 'multi_checkbox'),
   433         is_radio_buttons=selected * (self.render_as == 'quant_radio'),
   492         is_radio_buttons=selected * (self.render_as == 'quant_radio'),
   434         )
   493         )
   435 
   494 
       
   495     # set required and has_comment selects
       
   496     context.update(dict(
       
   497         is_required = self.is_required,
       
   498         has_comment = self.has_comment,
       
   499         ))
       
   500 
   436     str_value = forms.util.smart_unicode(value) # normalize to string.
   501     str_value = forms.util.smart_unicode(value) # normalize to string.
   437     chained_choices = enumerate(chain(self.choices, choices))
   502     chained_choices = enumerate(chain(self.choices, choices))
   438     choices = {}
   503     choices = {}
   439 
   504 
   440     for i, (option_value, option_label) in chained_choices:
   505     for i, (option_value, option_label) in chained_choices:
   468   """
   533   """
   469   #TODO(james): Ensure that more than one quant cannot be selected
   534   #TODO(james): Ensure that more than one quant cannot be selected
   470 
   535 
   471   def __init__(self, *args, **kwargs):
   536   def __init__(self, *args, **kwargs):
   472     super(PickQuantField, self).__init__(*args, **kwargs)
   537     super(PickQuantField, self).__init__(*args, **kwargs)
       
   538 
       
   539 
       
   540 class LongTextarea(widgets.Textarea):
       
   541   """Set whether long question is required or allows comments.
       
   542   """
       
   543 
       
   544   def __init__(self, is_required, has_comment, attrs=None, editing=False):
       
   545     """Initialize widget and store editing mode.
       
   546 
       
   547     params:
       
   548       is_required: bool, controls selection in the 'required' extra field
       
   549       has_comments: bool, controls selection in the 'has_comment' extra field
       
   550       editing: bool, controls rendering as plain textarea or with extra fields
       
   551     """
       
   552 
       
   553     self.editing = editing
       
   554     self.is_required = is_required
       
   555     self.has_comment = has_comment
       
   556 
       
   557     super(LongTextarea, self).__init__(attrs)
       
   558 
       
   559   def render(self, name, value, attrs=None):
       
   560     """Render plain textarea or widget with extra fields.
       
   561 
       
   562     Extra fields are 'required' and 'has_comment'.
       
   563     """
       
   564 
       
   565     # plain text area
       
   566     output = super(LongTextarea, self).render(name, value, attrs)
       
   567 
       
   568     if self.editing:
       
   569       # add 'required' and 'has_comment' fields
       
   570       context = dict(name=name, is_required=self.is_required,
       
   571                      has_comment=self.has_comment)
       
   572       template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
       
   573       rendered = template.render(context=loader.Context(dict_=context))
       
   574       output =  rendered + output
       
   575 
       
   576       output = '<fieldset>' + output + '</fieldset>'
       
   577     return output
       
   578 
       
   579 
       
   580 class ShortTextInput(widgets.TextInput):
       
   581   """Set whether short answer question is required or allows comments.
       
   582   """
       
   583 
       
   584   def __init__(self, is_required, has_comment, attrs=None, editing=False):
       
   585     """Initialize widget and store editing mode.
       
   586 
       
   587     params:
       
   588       is_required: bool, controls selection in the 'required' extra field
       
   589       has_comments: bool, controls selection in the 'has_comment' extra field
       
   590       editing: bool, controls rendering as plain text input or with extra fields
       
   591     """
       
   592 
       
   593     self.editing = editing
       
   594     self.is_required = is_required
       
   595     self.has_comment = has_comment
       
   596 
       
   597     super(ShortTextInput, self).__init__(attrs)
       
   598 
       
   599   def render(self, name, value, attrs=None):
       
   600     """Render plain text input or widget with extra fields.
       
   601 
       
   602     Extra fields are 'required' and 'has_comment'.
       
   603     """
       
   604 
       
   605     # plain text area
       
   606     output = super(ShortTextInput, self).render(name, value, attrs)
       
   607 
       
   608     if self.editing:
       
   609       # add 'required' and 'has_comment' fields
       
   610       context = dict(name=name, is_required=self.is_required,
       
   611                      has_comment=self.has_comment)
       
   612       template = loader.get_template_from_string(REQUIRED_COMMENT_TPL)
       
   613       rendered = template.render(context=loader.Context(dict_=context))
       
   614       output =  rendered + output
       
   615 
       
   616       output = '<fieldset>' + output + '</fieldset>'
       
   617     return output
   473 
   618 
   474 
   619 
   475 class PickOneSelect(forms.Select):
   620 class PickOneSelect(forms.Select):
   476   """Stub for customizing the single choice select widget.
   621   """Stub for customizing the single choice select widget.
   477   """
   622   """
   662         value = post_dict.getlist(name)
   807         value = post_dict.getlist(name)
   663 
   808 
   664     response_dict[name] = value
   809     response_dict[name] = value
   665 
   810 
   666     # handle comments
   811     # handle comments
   667     comment_name = COMMENT_PREFIX + name
   812     if schema.getHasComment(name):
   668     if comment_name in post_dict:
   813       comment_name = COMMENT_PREFIX + name
   669       comment = post_dict.get(comment_name)
   814       comment = post_dict.get(comment_name)
   670       if comment:
   815       if comment:
   671         response_dict[comment_name] = comment
   816         response_dict[comment_name] = comment
   672 
   817 
   673   return response_dict
   818   return response_dict