# HG changeset patch # User Daniel Diniz # Date 1246226283 -7200 # Node ID dd1f94c3594cd13f94bd4df1445593ee09e0ba65 # Parent a24b2b4af87d1ea02f626311141660f1255d898c Start on adding ProjectSurvey and GradingProjectSurvey. New classes still need to be made for logic and views so the appropriate methods can be overwritten. Reviewed by: Lennard de Rijk diff -r a24b2b4af87d -r dd1f94c3594c app/soc/logic/models/survey.py --- a/app/soc/logic/models/survey.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/logic/models/survey.py Sun Jun 28 23:58:03 2009 +0200 @@ -35,6 +35,8 @@ from soc.models.program import Program from soc.models import student_project from soc.models.survey import Survey +from soc.models.survey import ProjectSurvey +from soc.models.survey import GradingProjectSurvey from soc.models.survey import SurveyContent from soc.models.survey_record import SurveyRecord from soc.models.survey_record_group import SurveyRecordGroup @@ -199,8 +201,6 @@ user.key().name(), str(students)) ) return False - this_student = students[0] - def getStudentforProject(self, user, project): """Get student projects for a given User. @@ -256,7 +256,7 @@ % (survey.key().name(), survey.taking_access)) return False - program = survey.scope + program = survey.scope or Program.get_by_key_name(survey.scope_path) for project in program.student_projects.fetch(1000): this_record_group = SurveyRecordGroup.all().filter( @@ -289,7 +289,7 @@ project.key().name(), this_record_group.final_status ) ) continue - # assign the new status to the project and s + # assign the new status to the project and surveyrecordgroup project.status = new_project_status this_record_group.final_status = new_project_status @@ -313,12 +313,14 @@ survey = survey entity user = survey taking user """ - this_program = survey.scope + + this_program = survey.scope or Program.get_by_key_name(survey.scope_path) + if 'mentor' in survey.taking_access: these_projects = self.getMentorProjects(user, this_program) - if 'student' in survey.taking_access: + elif 'student' in survey.taking_access: these_projects = self.getStudentProjects(user, this_program) logging.info(these_projects) @@ -403,4 +405,8 @@ # return the scope return entity.scope + logic = Logic() +# TODO separate project and grading logic into own class to overwrite methods +project_logic = Logic(model=ProjectSurvey) +grading_logic = Logic(model=GradingProjectSurvey) diff -r a24b2b4af87d -r dd1f94c3594c app/soc/logic/models/survey_record.py --- a/app/soc/logic/models/survey_record.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/logic/models/survey_record.py Sun Jun 28 23:58:03 2009 +0200 @@ -22,8 +22,12 @@ ] +from google.appengine.ext import db + from soc.logic.models import work from soc.models.survey_record import SurveyRecord +from soc.models.survey_record import ProjectSurveyRecord +from soc.models.survey_record import GradingProjectSurveyRecord class Logic(work.Logic): @@ -38,5 +42,69 @@ super(Logic, self).__init__(model=model, base_model=base_model, scope_logic=scope_logic) + def updateSurveyRecord(self, user, survey, survey_record, fields): + """ Create a new survey record, or get an existing one. + + params: + user = user taking survey + survey = survey entity + survey_record = existing record, if one exists + fields = submitted responses to survey fields + """ + + if survey_record: + create = False + for prop in survey_record.dynamic_properties(): + delattr(survey_record, prop) + else: + create = True + Record = self.getModel() + survey_record = Record(user=user, survey=survey) + + schema = eval(survey.survey_content.schema) + + for name, value in fields.items(): + # TODO(ajaksu) logic below can be improved now we have different models + if name == 'project': + project = student_project.StudentProject.get(value) + survey_record.project = project + elif name == 'grade': + survey_record.grade = GRADES[value] + else: + pick_multi = name in schema and schema[name]['type'] == 'pick_multi' + if pick_multi and hasattr(fields, 'getlist'): # it's a multidict + setattr(survey_record, name, ','.join(fields.getlist(name))) + else: + setattr(survey_record, name, value) + + # if creating evaluation record, set SurveyRecordGroup + db.put(survey_record) + return survey_record + logic = Logic() +# TODO separate project and grading logic into own class to overwrite methods +project_logic = Logic(model=ProjectSurveyRecord) +grading_logic = Logic(model=GradingProjectSurveyRecord) + + +def updateSurveyRecord(user, survey, survey_record, fields): + """Create a new survey record, or get an existing one. + + params: + user = user taking survey + survey = survey entity + survey_record = existing record, if one exists + fields = submitted responses to survey fields + """ + + # TODO(ajaksu) We should use class information here, but being careful about + # compatibility with existent records should the class change. + if hasattr(survey_record, 'grade'): + record_logic = grading_logic + elif hasattr(survey_record, 'project'): + record_logic = grading_logic + else: + record_logic = logic + + return record_logic.updateSurveyRecord(user, survey, survey_record, fields) diff -r a24b2b4af87d -r dd1f94c3594c app/soc/models/survey.py --- a/app/soc/models/survey.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/models/survey.py Sun Jun 28 23:58:03 2009 +0200 @@ -32,6 +32,7 @@ from django.utils.translation import ugettext import soc.models.work +from soc.models.program import Program class SurveyContent(db.Expando): @@ -95,14 +96,13 @@ # these are GSoC specific, so eventually we can subclass this SURVEY_TAKING_ACCESS = ['student', 'mentor', - 'student evaluation', - 'mentor evaluation', 'org_admin', 'user', 'public'] GRADE_OPTIONS = {'midterm':['mid_term_passed', 'mid_term_failed'], 'final':['final_passed', 'final_failed'], 'N/A':[] } + prefix = db.StringProperty(default='program', required=True, choices=['site', 'club', 'sponsor', 'program', 'org', 'user'], verbose_name=ugettext('Prefix')) @@ -156,3 +156,42 @@ #: Referenceproperty that specifies the content of this survey. survey_content = db.ReferenceProperty(SurveyContent, collection_name="survey_parent") + + #: Survey kind, a helper for handling the different classes on creation. + survey_kind = db.StringProperty(default='', required=False, + choices=('', 'project', 'grading')) + + def getRecords(self): + """Returns all SurveyRecords belonging to this survey. + """ + return self.survey_records + + +class ProjectSurvey(Survey): + """Survey for Students that have a StudentProject. + """ + + def __init__(self, *args, **kwargs): + super(ProjectSurvey, self).__init__(*args, **kwargs) + self.prefix = 'program' + self.taking_access = 'student' + self.scope = Program.get_by_key_name(self.scope_path) + + def getRecords(self): + """Returns all ProjectSurveyRecords belonging to this survey. + """ + return self.project_survey_records + + +class GradingProjectSurvey(ProjectSurvey): + """Survey for Mentors that have a StudentProject. + """ + + def __init__(self, *args, **kwargs): + super(GradingProjectSurvey, self).__init__(*args, **kwargs) + self.taking_access = 'mentor' + + def getRecords(self): + """Returns all GradingProjectSurveyRecords belonging to this survey. + """ + return self.grading_survey_records diff -r a24b2b4af87d -r dd1f94c3594c app/soc/models/survey_record.py --- a/app/soc/models/survey_record.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/models/survey_record.py Sun Jun 28 23:58:03 2009 +0200 @@ -16,8 +16,9 @@ """SurveyRecord represents a single Survey result. -SurveyRecordGroup represents a cluster (mentor/student) of SurveyRecords -for an evaluation period. +ProjectSurveyRecord allows linking two result sets by StudentProject. + +GradingProjectSurveyRecord stores the grade in an evaluation survey. """ __authors__ = [ @@ -32,37 +33,24 @@ from django.utils.translation import ugettext from soc.models.survey import Survey +from soc.models.survey import GradingProjectSurvey +from soc.models.survey import ProjectSurvey import soc.models.student_project import soc.models.user -class SurveyRecord(db.Expando): +class BaseSurveyRecord(db.Expando): """Record produced each time Survey is taken. Like SurveyContent, this model includes dynamic properties corresponding to the fields of the survey. - - This also contains a Binary grade value that can be added/edited - by the administrator of the Survey. """ - #: The survey for which this entity is a record. - survey = db.ReferenceProperty(Survey, collection_name="survey_records") - #: Reference to the User entity which took this survey. user = db.ReferenceProperty(reference_class=soc.models.user.User, required=True, collection_name="surveys_taken", verbose_name=ugettext('Created by')) - #: Reference to the Project that this record belongs to. - # TODO should be moved to its own subclass - project = db.ReferenceProperty(soc.models.student_project.StudentProject, - collection_name="survey_records") - - #: Grade given to the project that this survey is about. - # TODO should be moved to its own subclass - grade = db.BooleanProperty(required=False) - #: Date when this record was created. created = db.DateTimeProperty(auto_now_add=True) @@ -75,8 +63,59 @@ Right now it gets all dynamic values, but it could also be confined to the SurveyContent entity linked to the survey entity. """ - survey_order = self.survey.survey_content.getSurveyOrder() + survey_order = self.getSurvey().survey_content.getSurveyOrder() values = [] for position, property in survey_order.items(): values.insert(position, getattr(self, property, None)) return values + + +# TODO(ajaksu) think of a better way to handle the survey reference +class SurveyRecord(BaseSurveyRecord): + + #: The survey for which this entity is a record. + survey = db.ReferenceProperty(Survey, collection_name="survey_records") + + def getSurvey(self): + """Returns the Survey belonging to this record. + """ + return self.survey + +class ProjectSurveyRecord(SurveyRecord): + """Record linked to a Project, enabling to store which Projects had their + Survey done. + """ + + #: The survey for which this entity is a record. + project_survey = db.ReferenceProperty(ProjectSurvey, + collection_name="project_survey_records") + + #: Reference to the Project that this record belongs to. + project = db.ReferenceProperty(soc.models.student_project.StudentProject, + collection_name="survey_records") + + def getSurvey(self): + """Returns the ProjectSurvey that belongs to this record. + """ + return self.project_survey + + +class GradingProjectSurveyRecord(ProjectSurveyRecord): + """Grading record for evaluation surveys. + + Represents the grading part of a evaluation survey group (usually a pair) + where the grading (e.g. Mentor's) survey is linked to a non-grading (e.g + Student's) one by a project. + """ + + #: The survey for which this entity is a record. + grading_survey = db.ReferenceProperty(GradingProjectSurvey, + collection_name="grading_survey_records") + + #: Required grade given to the project that this survey is about. + grade = db.BooleanProperty(required=True) + + def getSurvey(self): + """Returns the GradingProjectSurvey that belongs to this record. + """ + return self.grading_survey diff -r a24b2b4af87d -r dd1f94c3594c app/soc/models/survey_record_group.py --- a/app/soc/models/survey_record_group.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/models/survey_record_group.py Sun Jun 28 23:58:03 2009 +0200 @@ -26,12 +26,15 @@ from google.appengine.ext import db -from soc.models.survey_record import SurveyRecord +from soc.models.survey_record import GradingProjectSurveyRecord +from soc.models.survey_record import ProjectSurveyRecord import soc.models.user class SurveyRecordGroup(db.Expando): - """Because Mentors and Students take different surveys, + """Explicitly group SurveyRecords with a common project. + + Because Mentors and Students take different surveys, we cannot simply link survey records by a common project and survey. Instead, we establish a SurveyRecordGroup. @@ -44,12 +47,15 @@ against unpredictable behavior. """ + # TODO Create SurveyGroup model that contains the two Surveys as to make + # it possible to setup which surveys should be grouped. + #: Mentor SurveyRecord for this evaluation. - mentor_record = db.ReferenceProperty(SurveyRecord, required=False, + mentor_record = db.ReferenceProperty(GradingProjectSurveyRecord, required=False, collection_name='mentor_record_groups') #: Student SurveyRecord for this evaluation. - student_record = db.ReferenceProperty(SurveyRecord, required=False, + student_record = db.ReferenceProperty(ProjectSurveyRecord, required=False, collection_name='student_record_groups') #: Project for this evaluation. diff -r a24b2b4af87d -r dd1f94c3594c app/soc/views/helper/surveys.py --- a/app/soc/views/helper/surveys.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/views/helper/surveys.py Sun Jun 28 23:58:03 2009 +0200 @@ -26,6 +26,7 @@ from itertools import chain import datetime +import logging from google.appengine.ext.db import djangoforms @@ -95,7 +96,7 @@ return self.survey_fields = {} - schema = eval(self.survey_content.schema) + schema = SurveyContentSchema(self.survey_content.schema) has_record = (not self.editing) and self.survey_record extra_attrs = {} @@ -105,7 +106,7 @@ read_only = self.read_only if not read_only: - deadline = self.survey_content.survey_parent.get().deadline + deadline = self.survey_content.survey_parent.get().survey_end read_only = deadline and (datetime.datetime.now() > deadline) else: extra_attrs['disabled'] = 'disabled' diff -r a24b2b4af87d -r dd1f94c3594c app/soc/views/models/survey.py --- a/app/soc/views/models/survey.py Sun Jun 28 22:12:33 2009 +0200 +++ b/app/soc/views/models/survey.py Sun Jun 28 23:58:03 2009 +0200 @@ -29,18 +29,21 @@ import StringIO import string +from google.appengine.ext import db + from django import forms from django import http from django.utils import simplejson -from google.appengine.ext import db - from soc.cache import home from soc.logic import cleaning from soc.logic import dicts from soc.logic.models.survey import GRADES from soc.logic.models.survey import logic as survey_logic +from soc.logic.models.survey import project_logic +from soc.logic.models.survey import grading_logic from soc.logic.models.survey_record import logic as results_logic +from soc.logic.models.survey_record import updateSurveyRecord from soc.logic.models.user import logic as user_logic from soc.models.survey import Survey from soc.models.survey_record import SurveyRecord @@ -108,6 +111,7 @@ rights['grade'] = ['checkIsSurveyGradable'] new_params = {} + # TODO(ajaksu) pass logic in a way views can use them new_params['logic'] = survey_logic new_params['rights'] = rights @@ -268,8 +272,8 @@ else: # save/update the submitted survey context['notice'] = "Survey Submission Saved" - survey_record = survey_logic.updateSurveyRecord(user, survey, - survey_record, request.POST) + survey_record = updateSurveyRecord(user, survey, survey_record, + request.POST) survey_content = survey.survey_content if not survey_record and read_only: @@ -392,6 +396,19 @@ super(View, self)._editContext(request, context) + def createPost(self, request, context, params): + + # TODO(ajaksu) create new View class for other surveys + survey_type = request.POST.get('survey_type') + if not survey_type: + self._logic = params['logic'] = survey_logic + elif survey_type == 'project': + self._logic = params['logic'] = project_logic + elif survey_type == 'grading': + self._logic = params['logic'] = grading_logic + + return super(View, self).createPost(request, context, params) + def _editPost(self, request, entity, fields): """See base.View._editPost(). @@ -908,13 +925,13 @@ properties = leading + survey.survey_content.orderedProperties() try: - first = survey.survey_records.run().next() + first = survey.getRecords().run().next() except StopIteration: # bail out early if survey_records.run() is empty return header, survey.link_id # generate results list - recs = survey.survey_records.run() + recs = survey.getRecords().run() recs = _get_records(recs, properties) # write results to CSV