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 """ |