Added logic for Survey and SurveyRecord.
authorJames Levy <jamesalexanderlevy@gmail.com>
Sun, 28 Jun 2009 14:18:01 +0200
changeset 2431 800a020c9bcf
parent 2430 c9a5bf5ed3c5
child 2432 636dfd5381c2
Added logic for Survey and SurveyRecord. The logic for the SurveyRecord will be seperated within the upcoming days. Patch by: James Levy, Daniel Diniz, Lennard de Rijk Reviewed by: Lennard de Rijk
app/soc/logic/models/survey.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/logic/models/survey.py	Sun Jun 28 14:18:01 2009 +0200
@@ -0,0 +1,423 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2009 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Survey (Model) query functions.
+"""
+
+__authors__ = [
+  '"Daniel Diniz" <ajaksu@gmail.com>',
+  '"James Levy" <jamesalexanderlevy@gmail.com>',
+  '"Lennard de Rijk" <ljvderijk@gmail.com>',
+  ]
+
+
+import logging
+
+from google.appengine.ext import db
+
+from soc.cache import sidebar
+from soc.logic.models import linkable as linkable_logic
+from soc.logic.models.news_feed import logic as newsfeed_logic
+from soc.logic.models.user import logic as user_logic
+from soc.logic.models import work
+from soc.models.program import Program
+from soc.models import student_project
+from soc.models.survey import Survey
+from soc.models.survey import SurveyContent
+from soc.models.survey_record import SurveyRecord
+from soc.models.survey_record_group import SurveyRecordGroup
+from soc.models.work import Work
+
+#TODO(James): Ensure this facilitates variable # of surveys 
+GRADES = {'pass': True, 'fail': False}
+PROJECT_STATUSES = {
+'accepted': {True: 'mid_term_passed', False: 'mid_term_failed'},
+'mid_term_passed': {True: 'passed', False: 'final_failed'}
+}
+
+class Logic(work.Logic):
+  """Logic methods for the Survey model.
+  """
+
+  def __init__(self, model=Survey, base_model=Work,
+               scope_logic=linkable_logic):
+    """Defines the name, key_name and model for this entity.
+    """
+
+    super(Logic, self).__init__(model=model, base_model=base_model,
+                                scope_logic=scope_logic)
+
+  def createSurvey(self, survey_fields, schema, survey_content=False):
+    """Create a new survey from prototype.
+
+    params:
+      survey_fields = dict of survey field items (see SurveyContent model)
+      schema = metadata about survey fields (SurveyContent.schema)
+      survey_content = existing SurveyContent entity
+    """
+
+    if not survey_content:
+      survey_content = SurveyContent()
+    else:
+      # wipe clean existing dynamic properties if they exist
+      for prop in survey_content.dynamic_properties():
+        delattr(survey_content, prop)
+
+    for name, value in survey_fields.items():
+      setattr(survey_content, name, value)
+
+    survey_content.schema = str(schema)
+
+    db.put(survey_content)
+
+    return survey_content
+
+  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
+      survey_record = SurveyRecord(user=user, survey=survey)
+
+    schema = eval(survey.survey_content.schema)
+
+    for name, value in fields.items():
+      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)
+    if 'evaluation' in survey.taking_access and create:
+      if not project: return False
+      role = self.getUserRole(user, survey, project)
+      survey_record_group = self.setSurveyRecordGroup(survey,
+      survey_record, project)
+      if survey_record_group:  db.put(survey_record_group)
+
+    return survey_record
+
+  def setSurveyRecordGroup(self, survey, survey_record, project):
+    """First looks for an existing SurveyRecordGroup, using the
+    project and its current status as a filter.
+
+    IOW SurveyRecordGroup cannot consist of surveys taken with
+    two different statuses.
+
+    This means that a student cannot take a survey after the mentor
+    has taken the accompanying survey and the project has since
+    changed. (Assuming we want this strict behavior)
+
+    params:
+      survey = survey entity
+      survey_record = saved response to survey
+      project = student project for survey taker
+    """
+
+    group_query = SurveyRecordGroup.all(
+    ).filter("project = ", project
+    ).filter("initial_status = ", project.status
+    )
+
+    if survey.taking_access == 'mentor evaluation':
+      survey_record_group = group_query.filter(
+      "mentor = ", None ).get()
+    elif survey.taking_access == 'student evaluation':
+      survey_record_group = group_query.filter(
+      "student = ", None ).get()
+
+    if not survey_record_group:
+      #create Survey Record Group if it doesn't already exist
+      survey_record_group = SurveyRecordGroup(
+      project=project,
+      initial_status = project.status
+      )
+
+    if survey.taking_access == 'mentor evaluation':
+      survey_record_group.mentor_record = survey_record
+    elif survey.taking_access == 'student evaluation':
+      survey_record_group.student_record = survey_record
+
+    return survey_record_group
+
+  def getUserRole(self, user, survey, project):
+    """Gets the role of a user for a project, used for SurveyRecordGroup.
+
+    params:
+      user: user taking survey
+      survey: survey entity
+      project: student project for this user
+    """
+
+    if survey.taking_access == 'mentor evaluation':
+      mentors = self.getMentorforProject(user, project)
+
+      if len(mentors) < 1 or len(mentors) > 1:
+        logging.warning('Unable to determine mentor for \
+        user %s. Results returned: %s ' % (
+        user.key().name(), str(mentors)) )
+        return False
+
+      this_mentor = mentors[0]
+
+    if survey.taking_access == 'student evaluation':
+      students = self.getStudentforProject(user, project)
+
+      if len(students) < 1 or len(students) > 1:
+        logging.warning('Unable to determine student for \
+        user %s. Results returned: %s ' % (
+        user.key().name(), str(students)) )
+        return False
+
+      this_student = students[0]
+
+  def getStudentforProject(self, user, project):
+    """Get student projects for a given User.
+
+    params:
+      user = survey taking user
+      project = survey taker's student project
+    """
+    from soc.logic.models.student import logic as student_logic
+    import soc.models.student
+
+    # TODO this should be done per Student or Program
+    # TODO filter for accepted, midterm_passed, etc?
+    user_students = student_logic.getForFields({'user': user}) 
+    if not user_students: return []
+    return set([project.student for project in sum(
+    (list(s.student_projects.run())
+    for s in user_students), []) if project.key() == project.key()])
+
+  def getMentorforProject(self, user, project):
+    """Get Student Projects that are being mentored by the given User.
+
+    params:
+      user = survey taking user
+      project = survey taker's student project
+    """
+
+    from soc.logic.models.mentor import logic as mentor_logic
+    import soc.models.mentor
+
+    # TODO filter for accepted, midterm_passed, etc?
+    # TODO this should be done a program basis not user
+
+    user_mentors = mentor_logic.getForFields({'user': user}) 
+
+    if not user_mentors:
+      return []
+
+    return set([project.mentor for project in sum(
+            (list(mentor.student_projects.run())
+             for mentor in user_mentors), [])
+        if project.key() == project.key()])
+
+  def activateGrades(self, survey):
+    """Activates the grades on a Grading Survey.
+
+    TODO(James) Fix this Docstring
+
+    params:
+      survey = survey entity
+    """
+    if survey.taking_access != "mentor evaluation":
+      logging.error("Cannot grade survey %s with taking access %s"
+      % (survey.key().name(), survey.taking_access))
+      return False
+
+    program = survey.scope
+
+    for project in program.student_projects.fetch(1000):
+      this_record_group = SurveyRecordGroup.all().filter(
+      "project = ", project).filter(
+      "initial_status = ", project.status).get()
+
+      if not this_record_group:
+         logging.warning('neither mentor nor student has \
+         taken the survey for project %s' % project.key().name() )
+         continue
+
+      if not this_record_group.mentor_record:
+        # student has taken survey, but not mentor
+        logging.warning('not continuing without mentor record...')
+        continue
+
+      status_options = PROJECT_STATUSES.get(project.status)
+
+      if not status_options:
+        logging.warning('unable to find status options for project \
+        status %s' % project.status)
+        continue
+
+      new_project_grade = this_record_group.mentor_record.grade
+      new_project_status = status_options.get(new_project_grade)
+
+      if getattr(this_record_group, 'final_status'):
+         logging.warning('project %s record group should not \
+         yet have a final status %s' % (
+         project.key().name(), this_record_group.final_status ) )
+         continue
+
+      # assign the new status to the project and s
+      project.status = new_project_status
+      this_record_group.final_status = new_project_status
+
+  def getKeyNameFromPath(self, path):
+    """Gets survey key name from a request path.
+
+    params:
+      path = path of the current request
+    """
+
+    # TODO determine if kwargs in the request contains this information
+    return '/'.join(path.split('/')[-4:]).split('?')[0]
+
+  def getProjects(self, survey, user):
+    """Get projects linking user to a program.
+
+    Serves as access handler (since no projects == no access).
+    And retrieves projects to choose from (if mentors have >1 projects).
+
+    params:
+      survey = survey entity
+      user = survey taking user
+    """
+    this_program = survey.scope
+
+    if 'mentor' in survey.taking_access:
+      these_projects = self.getMentorProjects(user, this_program)
+
+    if 'student' in survey.taking_access:
+      these_projects = self.getStudentProjects(user, this_program)
+
+    logging.info(these_projects)
+
+    if len(these_projects) == 0:
+      return False
+
+    return these_projects
+
+  def getDebugUser(self, survey, this_program):
+    """Debugging method impersonates other roles.
+
+    Tests taking survey, saving response, and grading.
+
+    params:
+      survey = survey entity
+      this_program = program scope of survey
+    """
+
+    if 'mentor' in survey.taking_access:
+      from soc.models.mentor import Mentor
+      role = Mentor.get_by_key_name(
+      this_program.key().name() + "/org_1/test")
+
+    if 'student' in survey.taking_access:
+      from soc.models.student import Student
+      role = Student.get_by_key_name(
+      this_program.key().name() + "/test")
+
+    if role: return role.user
+
+  def getKeyValuesFromEntity(self, entity):
+    """See base.Logic.getKeyNameValues.
+    """
+
+    return [entity.prefix, entity.scope_path, entity.link_id]
+
+  def getKeyValuesFromFields(self, fields):
+    """See base.Logic.getKeyValuesFromFields.
+    """
+
+    return [fields['prefix'], fields['scope_path'], fields['link_id']]
+
+  def getKeyFieldNames(self):
+    """See base.Logic.getKeyFieldNames.
+    """
+
+    return ['prefix', 'scope_path', 'link_id']
+
+  def getScope(self, entity):
+    """Gets Scope for entity.
+
+    params:
+      entity = Survey entity
+    """
+
+    if getattr(entity, 'scope', None):
+      return entity.scope
+
+    import soc.models.program
+    import soc.models.organization
+    import soc.models.user
+    import soc.models.site
+
+    # use prefix to generate dict key
+    scope_types = {"program": soc.models.program.Program,
+    "org": soc.models.organization.Organization,
+    "user": soc.models.user.User,
+    "site": soc.models.site.Site}
+
+    # determine the type of the scope
+    scope_type = scope_types.get(entity.prefix)
+
+    if not scope_type:
+      # no matching scope type found
+      raise AttributeError('No Matching Scope type found for %s' % entity.prefix)
+
+    # set the scope and update the entity
+    entity.scope = scope_type.get_by_key_name(entity.scope_path)
+    entity.put()
+
+    # return the scope
+    return entity.scope
+
+logic = Logic()
+
+
+class ResultsLogic(work.Logic):
+  """Logic methods for listing results for Surveys.
+  """
+
+  def __init__(self, model=SurveyRecord,
+               base_model=None, scope_logic=None):
+    """Defines the name, key_name and model for this entity.
+    """
+
+    super(ResultsLogic, self).__init__(model=model, base_model=base_model,
+                                scope_logic=scope_logic)
+
+
+results_logic = ResultsLogic()