# HG changeset patch # User Daniel Diniz # Date 1246623563 -7200 # Node ID d612b48e6e121f0e35f6685e8fabb3979fcf34b2 # Parent ecc16ffe174b5adab92b9f4e3a9e6fd456daed50 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 diff -r ecc16ffe174b -r d612b48e6e12 app/soc/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', diff -r ecc16ffe174b -r d612b48e6e12 app/soc/views/helper/surveys.py --- 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 diff -r ecc16ffe174b -r d612b48e6e12 app/soc/views/models/survey.py --- 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: