64 read_only: controls whether the survey taking UI allows data entry. |
64 read_only: controls whether the survey taking UI allows data entry. |
65 editing: controls whether to show the edit or show form. |
65 editing: controls whether to show the edit or show form. |
66 """ |
66 """ |
67 |
67 |
68 self.kwargs = kwargs |
68 self.kwargs = kwargs |
69 self.survey_content = self.kwargs.get('survey_content', None) |
69 self.survey_content = self.kwargs.pop('survey_content', None) |
70 self.this_user = self.kwargs.get('this_user', None) |
70 self.this_user = self.kwargs.pop('this_user', None) |
71 self.project = self.kwargs.get('project', None) |
71 self.project = self.kwargs.pop('project', None) |
72 self.survey_record = self.kwargs.get('survey_record', None) |
72 self.survey_record = self.kwargs.pop('survey_record', None) |
73 |
73 |
74 del self.kwargs['survey_content'] |
74 self.read_only = self.kwargs.pop('read_only', None) |
75 del self.kwargs['this_user'] |
75 self.editing = self.kwargs.pop('editing', None) |
76 del self.kwargs['project'] |
76 |
77 del self.kwargs['survey_record'] |
77 self.fields_map = dict( |
78 |
78 long_answer=self.addLongField, |
79 self.read_only = self.kwargs.get('read_only', None) |
79 short_answer=self.addShortField, |
80 if 'read_only' in self.kwargs: |
80 selection=self.addSingleField, |
81 del self.kwargs['read_only'] |
81 pick_multi=self.addMultiField, |
82 |
82 pick_quant=self.addQuantField, |
83 self.editing = self.kwargs.get('editing', None) |
83 ) |
84 if 'editing' in self.kwargs: |
|
85 del self.kwargs['editing'] |
|
86 |
84 |
87 super(SurveyForm, self).__init__(*args, **self.kwargs) |
85 super(SurveyForm, self).__init__(*args, **self.kwargs) |
88 |
86 |
89 def getFields(self): |
87 def getFields(self): |
90 """Build the SurveyContent (questions) form fields. |
88 """Build the SurveyContent (questions) form fields. |
123 comment = getattr(self.survey_record, 'comment_for_' + field) |
121 comment = getattr(self.survey_record, 'comment_for_' + field) |
124 else: |
122 else: |
125 # use prompts set by survey creator |
123 # use prompts set by survey creator |
126 value = getattr(self.survey_content, field) |
124 value = getattr(self.survey_content, field) |
127 |
125 |
128 if field not in schema: |
126 label = schema.getLabel(field) |
129 logging.error('field %s not found in schema %s' % |
127 if label is None: |
130 (field, str(schema) ) ) |
128 continue |
131 continue |
|
132 elif 'question' in schema[field]: |
|
133 label = schema[field].get('question', None) or field |
|
134 else: |
|
135 label = field |
|
136 |
129 |
137 # dispatch to field-specific methods |
130 # dispatch to field-specific methods |
138 if schema[field]["type"] == "long_answer": |
131 addField = self.fields_map[schema.getType(field)] |
139 self.addLongField(field, value, extra_attrs, label=label, |
132 addField(field, value, extra_attrs, schema, label=label, comment=comment) |
140 comment=comment) |
|
141 elif schema[field]["type"] == "short_answer": |
|
142 self.addShortField(field, value, extra_attrs, label=label, |
|
143 comment=comment) |
|
144 elif schema[field]["type"] == "selection": |
|
145 self.addSingleField(field, value, extra_attrs, schema, label=label, |
|
146 comment=comment) |
|
147 elif schema[field]["type"] == "pick_multi": |
|
148 self.addMultiField(field, value, extra_attrs, schema, label=label, |
|
149 comment=comment) |
|
150 elif schema[field]["type"] == "pick_quant": |
|
151 self.addQuantField(field, value, extra_attrs, schema, label=label, |
|
152 comment=comment) |
|
153 |
133 |
154 return self.insertFields() |
134 return self.insertFields() |
155 |
135 |
156 def insertFields(self): |
136 def insertFields(self): |
157 """Add ordered fields to self.fields. |
137 """Add ordered fields to self.fields. |
167 property = 'comment_for_' + property |
147 property = 'comment_for_' + property |
168 self.fields.insert(position - 1, property, |
148 self.fields.insert(position - 1, property, |
169 self.survey_fields[property]) |
149 self.survey_fields[property]) |
170 return self.fields |
150 return self.fields |
171 |
151 |
172 def addLongField(self, field, value, attrs, req=False, label='', tip='', |
152 def addLongField(self, field, value, attrs, schema, req=False, label='', tip='', |
173 comment=''): |
153 comment=''): |
174 """Add a long answer fields to this form. |
154 """Add a long answer fields to this form. |
175 |
|
176 params: |
|
177 field: the current field |
|
178 value: the initial value for this field |
|
179 attrs: additional attributes for field |
|
180 req: required bool |
|
181 label: label for field |
|
182 tip: tooltip text for field |
|
183 comment: initial comment value for field |
|
184 """ |
|
185 |
|
186 widget = widgets.Textarea(attrs=attrs) |
|
187 |
|
188 if not tip: |
|
189 tip = 'Please provide a long answer to this question.' |
|
190 |
|
191 question = CharField(help_text=tip, required=req, label=label, |
|
192 widget=widget, initial=value) |
|
193 self.survey_fields[field] = question |
|
194 |
|
195 if not self.editing: |
|
196 widget = widgets.Textarea(attrs=attrs) |
|
197 comment = CharField(help_text=tip, required=False, label='Comments', |
|
198 widget=widget, initial=comment) |
|
199 self.survey_fields['comment_for_' + field] = comment |
|
200 |
|
201 def addShortField(self, field, value, attrs, req=False, label='', tip='', |
|
202 comment=''): |
|
203 """Add a short answer fields to this form. |
|
204 |
|
205 params: |
|
206 field: the current field |
|
207 value: the initial value for this field |
|
208 attrs: additional attributes for field |
|
209 req: required bool |
|
210 label: label for field |
|
211 tip: tooltip text for field |
|
212 comment: initial comment value for field |
|
213 """ |
|
214 |
|
215 attrs['class'] = "text_question" |
|
216 widget = widgets.TextInput(attrs=attrs) |
|
217 |
|
218 if not tip: |
|
219 tip = 'Please provide a short answer to this question.' |
|
220 |
|
221 question = CharField(help_text=tip, required=req, label=label, |
|
222 widget=widget, max_length=140, initial=value) |
|
223 self.survey_fields[field] = question |
|
224 |
|
225 if not self.editing: |
|
226 widget = widgets.Textarea(attrs=attrs) |
|
227 comment = CharField(help_text=tip, required=False, label='Comments', |
|
228 widget=widget, initial=comment) |
|
229 self.survey_fields['comment_for_' + field] = comment |
|
230 |
|
231 def addSingleField(self, field, value, attrs, schema, req=False, label='', |
|
232 tip='', comment=''): |
|
233 """Add a selection field to this form. |
|
234 |
155 |
235 params: |
156 params: |
236 field: the current field |
157 field: the current field |
237 value: the initial value for this field |
158 value: the initial value for this field |
238 attrs: additional attributes for field |
159 attrs: additional attributes for field |
241 label: label for field |
162 label: label for field |
242 tip: tooltip text for field |
163 tip: tooltip text for field |
243 comment: initial comment value for field |
164 comment: initial comment value for field |
244 """ |
165 """ |
245 |
166 |
246 if self.editing: |
167 widget = widgets.Textarea(attrs=attrs) |
247 kind = schema[field]["type"] |
168 |
248 render = schema[field]["render"] |
|
249 widget = UniversalChoiceEditor(kind, render) |
|
250 else: |
|
251 widget = WIDGETS[schema[field]['render']](attrs=attrs) |
|
252 |
|
253 these_choices = [] |
|
254 # add all properties, but select chosen one |
|
255 options = getattr(self.survey_content, field) |
|
256 has_record = not self.editing and self.survey_record |
|
257 if has_record and hasattr(self.survey_record, field): |
|
258 these_choices.append((value, value)) |
|
259 if value in options: |
|
260 options.remove(value) |
|
261 |
|
262 for option in options: |
|
263 these_choices.append((option, option)) |
|
264 if not tip: |
169 if not tip: |
265 tip = 'Please select an answer this question.' |
170 tip = 'Please provide a long answer to this question.' |
266 |
171 |
267 question = PickOneField(help_text=tip, required=req, label=label, |
172 question = CharField(help_text=tip, required=req, label=label, |
268 choices=tuple(these_choices), widget=widget) |
173 widget=widget, initial=value) |
|
174 |
269 self.survey_fields[field] = question |
175 self.survey_fields[field] = question |
270 |
176 self.addCommentField(field, comment, attrs, tip) |
271 if not self.editing: |
177 |
272 widget = widgets.Textarea(attrs=attrs) |
178 def addShortField(self, field, value, attrs, schema, req=False, label='', tip='', |
273 comment = CharField(help_text=tip, required=False, label='Comments', |
179 comment=''): |
274 widget=widget, initial=comment) |
180 """Add a short answer fields to this form. |
275 self.survey_fields['comment_for_' + field] = comment |
|
276 |
|
277 def addMultiField(self, field, value, attrs, schema, req=False, label='', |
|
278 tip='', comment=''): |
|
279 """Add a pick_multi field to this form. |
|
280 |
181 |
281 params: |
182 params: |
282 field: the current field |
183 field: the current field |
283 value: the initial value for this field |
184 value: the initial value for this field |
284 attrs: additional attributes for field |
185 attrs: additional attributes for field |
287 label: label for field |
188 label: label for field |
288 tip: tooltip text for field |
189 tip: tooltip text for field |
289 comment: initial comment value for field |
190 comment: initial comment value for field |
290 """ |
191 """ |
291 |
192 |
292 if self.editing: |
193 attrs['class'] = "text_question" |
293 kind = schema[field]["type"] |
194 widget = widgets.TextInput(attrs=attrs) |
294 render = schema[field]["render"] |
195 |
295 widget = UniversalChoiceEditor(kind, render) |
|
296 else: |
|
297 widget = WIDGETS[schema[field]['render']](attrs=attrs) |
|
298 |
|
299 # TODO(ajaksu) need to allow checking checkboxes by default |
|
300 if self.survey_record and isinstance(value, basestring): |
|
301 # pass value as 'initial' so MultipleChoiceField renders checked boxes |
|
302 value = value.split(',') |
|
303 else: |
|
304 value = None |
|
305 |
|
306 these_choices = [(v,v) for v in getattr(self.survey_content, field)] |
|
307 if not tip: |
196 if not tip: |
308 tip = 'Please select one or more of these choices.' |
197 tip = 'Please provide a short answer to this question.' |
309 |
198 |
310 question = PickManyField(help_text=tip, required=req, label=label, |
199 question = CharField(help_text=tip, required=req, label=label, |
311 choices=tuple(these_choices), widget=widget, |
200 widget=widget, max_length=140, initial=value) |
312 initial=value) |
201 |
313 self.survey_fields[field] = question |
202 self.survey_fields[field] = question |
314 if not self.editing: |
203 self.addCommentField(field, comment, attrs, tip) |
315 widget = widgets.Textarea(attrs=attrs) |
204 |
316 comment = CharField(help_text=tip, required=False, label='Comments', |
205 def addSingleField(self, field, value, attrs, schema, req=False, label='', |
317 widget=widget, initial=comment) |
206 tip='', comment=''): |
318 self.survey_fields['comment_for_' + field] = comment |
207 """Add a selection field to this form. |
319 |
|
320 def addQuantField(self, field, value, attrs, schema, req=False, label='', |
|
321 tip='', comment=''): |
|
322 """Add a pick_quant field to this form. |
|
323 |
208 |
324 params: |
209 params: |
325 field: the current field |
210 field: the current field |
326 value: the initial value for this field |
211 value: the initial value for this field |
327 attrs: additional attributes for field |
212 attrs: additional attributes for field |
330 label: label for field |
215 label: label for field |
331 tip: tooltip text for field |
216 tip: tooltip text for field |
332 comment: initial comment value for field |
217 comment: initial comment value for field |
333 """ |
218 """ |
334 |
219 |
335 if self.editing: |
220 widget = schema.getWidget(field, self.editing, attrs) |
336 kind = schema[field]["type"] |
221 |
337 render = schema[field]["render"] |
222 these_choices = [] |
338 widget = UniversalChoiceEditor(kind, render) |
223 # add all properties, but select chosen one |
|
224 options = getattr(self.survey_content, field) |
|
225 has_record = not self.editing and self.survey_record |
|
226 if has_record and hasattr(self.survey_record, field): |
|
227 these_choices.append((value, value)) |
|
228 if value in options: |
|
229 options.remove(value) |
|
230 |
|
231 for option in options: |
|
232 these_choices.append((option, option)) |
|
233 if not tip: |
|
234 tip = 'Please select an answer this question.' |
|
235 |
|
236 question = PickOneField(help_text=tip, required=req, label=label, |
|
237 choices=tuple(these_choices), widget=widget) |
|
238 |
|
239 self.survey_fields[field] = question |
|
240 self.addCommentField(field, comment, attrs, tip) |
|
241 |
|
242 def addMultiField(self, field, value, attrs, schema, req=False, label='', |
|
243 tip='', comment=''): |
|
244 """Add a pick_multi field to this form. |
|
245 |
|
246 params: |
|
247 field: the current field |
|
248 value: the initial value for this field |
|
249 attrs: additional attributes for field |
|
250 schema: schema for survey |
|
251 req: required bool |
|
252 label: label for field |
|
253 tip: tooltip text for field |
|
254 comment: initial comment value for field |
|
255 |
|
256 """ |
|
257 |
|
258 widget = schema.getWidget(field, self.editing, attrs) |
|
259 |
|
260 # TODO(ajaksu) need to allow checking checkboxes by default |
|
261 if self.survey_record and isinstance(value, basestring): |
|
262 # pass value as 'initial' so MultipleChoiceField renders checked boxes |
|
263 value = value.split(',') |
339 else: |
264 else: |
340 widget = WIDGETS[schema[field]['render']](attrs=attrs) |
265 value = None |
|
266 |
|
267 these_choices = [(v,v) for v in getattr(self.survey_content, field)] |
|
268 if not tip: |
|
269 tip = 'Please select one or more of these choices.' |
|
270 |
|
271 question = PickManyField(help_text=tip, required=req, label=label, |
|
272 choices=tuple(these_choices), widget=widget, |
|
273 initial=value) |
|
274 |
|
275 self.survey_fields[field] = question |
|
276 self.addCommentField(field, comment, attrs, tip) |
|
277 |
|
278 def addQuantField(self, field, value, attrs, schema, req=False, label='', |
|
279 tip='', comment=''): |
|
280 """Add a pick_quant field to this form. |
|
281 |
|
282 params: |
|
283 field: the current field |
|
284 value: the initial value for this field |
|
285 attrs: additional attributes for field |
|
286 schema: schema for survey |
|
287 req: required bool |
|
288 label: label for field |
|
289 tip: tooltip text for field |
|
290 comment: initial comment value for field |
|
291 |
|
292 """ |
|
293 |
|
294 widget = schema.getWidget(field, self.editing, attrs) |
341 |
295 |
342 if self.survey_record: |
296 if self.survey_record: |
343 value = value |
297 value = value |
344 else: |
298 else: |
345 value = None |
299 value = None |
350 |
304 |
351 question = PickQuantField(help_text=tip, required=req, label=label, |
305 question = PickQuantField(help_text=tip, required=req, label=label, |
352 choices=tuple(these_choices), widget=widget, |
306 choices=tuple(these_choices), widget=widget, |
353 initial=value) |
307 initial=value) |
354 self.survey_fields[field] = question |
308 self.survey_fields[field] = question |
|
309 self.addCommentField(field, comment, attrs, tip) |
|
310 |
|
311 def addCommentField(self, field, comment, attrs, tip): |
355 if not self.editing: |
312 if not self.editing: |
356 widget = widgets.Textarea(attrs=attrs) |
313 widget = widgets.Textarea(attrs=attrs) |
357 comment = CharField(help_text=tip, required=False, label='Comments', |
314 comment = CharField(help_text=tip, required=False, label='Comments', |
358 widget=widget, initial=comment) |
315 widget=widget, initial=comment) |
359 self.survey_fields['comment_for_' + field] = comment |
316 self.survey_fields['comment_for_' + field] = comment |
360 |
317 |
361 |
|
362 class Meta(object): |
318 class Meta(object): |
363 model = SurveyContent |
319 model = SurveyContent |
364 exclude = ['schema'] |
320 exclude = ['schema'] |
|
321 |
|
322 |
|
323 class SurveyContentSchema(object): |
|
324 """Abstract question metadata handling. |
|
325 """ |
|
326 |
|
327 def __init__(self, schema): |
|
328 self.schema = eval(schema) |
|
329 |
|
330 def getType(self, field): |
|
331 return self.schema[field]["type"] |
|
332 |
|
333 def getRender(self, field): |
|
334 return self.schema[field]["render"] |
|
335 |
|
336 def getWidget(self, field, editing, attrs): |
|
337 """Get survey editing or taking widget for choice questions. |
|
338 """ |
|
339 |
|
340 if editing: |
|
341 kind = self.getType(field) |
|
342 render = self.getRender(field) |
|
343 widget = UniversalChoiceEditor(kind, render) |
|
344 else: |
|
345 widget = WIDGETS[self.schema[field]['render']](attrs=attrs) |
|
346 return widget |
|
347 |
|
348 def getLabel(self, field): |
|
349 """Fetch the free text 'question' or use field name as label. |
|
350 """ |
|
351 |
|
352 if field not in self.schema: |
|
353 logging.error('field %s not found in schema %s' % |
|
354 (field, str(self.schema))) |
|
355 return |
|
356 elif 'question' in self.schema[field]: |
|
357 label = self.schema[field].get('question') or field |
|
358 else: |
|
359 label = field |
|
360 return label |
365 |
361 |
366 |
362 |
367 class UniversalChoiceEditor(widgets.Widget): |
363 class UniversalChoiceEditor(widgets.Widget): |
368 """Edit interface for choice questions. |
364 """Edit interface for choice questions. |
369 |
365 |
598 |
594 |
599 |
595 |
600 def getRoleSpecificFields(survey, user, this_project, survey_form, |
596 def getRoleSpecificFields(survey, user, this_project, survey_form, |
601 survey_record): |
597 survey_record): |
602 """For evaluations, mentors get required Project and Grade fields, and |
598 """For evaluations, mentors get required Project and Grade fields, and |
603 students get a required Project field. |
599 students get a required Project field. |
604 |
600 |
605 Because we need to get a list of the user's projects, we call the |
601 Because we need to get a list of the user's projects, we call the |
606 logic getProjects method, which doubles as an access check. |
602 logic getProjects method, which doubles as an access check. |
607 (No projects means that the survey cannot be taken.) |
603 (No projects means that the survey cannot be taken.) |
608 |
604 |
609 params: |
605 params: |
610 survey: the survey being taken |
606 survey: the survey being taken |
613 survey_form: the surveyForm widget for this survey |
609 survey_form: the surveyForm widget for this survey |
614 survey_record: an existing survey record for a user-project-survey combo, |
610 survey_record: an existing survey record for a user-project-survey combo, |
615 or None |
611 or None |
616 """ |
612 """ |
617 |
613 |
618 from django import forms |
|
619 |
|
620 field_count = len(eval(survey.survey_content.schema).items()) |
614 field_count = len(eval(survey.survey_content.schema).items()) |
621 these_projects = survey_logic.getProjects(survey, user) |
615 these_projects = survey_logic.getProjects(survey, user) |
622 if not these_projects: return False # no projects found |
616 if not these_projects: |
|
617 return False # no projects found |
623 |
618 |
624 project_pairs = [] |
619 project_pairs = [] |
625 #insert a select field with options for each project |
620 #insert a select field with options for each project |
626 for project in these_projects: |
621 for project in these_projects: |
627 project_pairs.append((project.key(), project.title)) |
622 project_pairs.append((project.key(), project.title)) |