diff -r afae8791ad97 -r 97117d341f62 app/soc/views/helper/surveys.py --- 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 = """ @@ -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 = '
' + output + '
' + output = '
' + output + '
' 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 = '
' + output + '
' + output = '
' + output + '
' 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. """