Start on adding ProjectSurvey and GradingProjectSurvey.
authorDaniel Diniz <ajaksu@gmail.com>
Sun, 28 Jun 2009 23:58:03 +0200
changeset 2442 dd1f94c3594c
parent 2441 a24b2b4af87d
child 2443 2e86dbd47907
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
app/soc/logic/models/survey.py
app/soc/logic/models/survey_record.py
app/soc/models/survey.py
app/soc/models/survey_record.py
app/soc/models/survey_record_group.py
app/soc/views/helper/surveys.py
app/soc/views/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)
--- 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)
--- 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
--- 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
--- 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.
--- 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'
--- 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