Added Surveys Tasks module to send reminders.
authorLennard de Rijk <ljvderijk@gmail.com>
Sat, 11 Jul 2009 23:18:14 +0200
changeset 2595 4c00831ef55c
parent 2594 dd300af0e7df
child 2596 56924ec61cd2
Added Surveys Tasks module to send reminders. All reminders are CC'ed to the org admins. Also added an index property on StudentProjects which is required for the reminder Tasks.
app/index.yaml
app/soc/tasks/surveys.py
app/soc/templates/soc/grading_project_survey/mail/reminder_gsoc.html
app/soc/templates/soc/project_survey/mail/reminder_gsoc.html
--- a/app/index.yaml	Sat Jul 11 23:15:22 2009 +0200
+++ b/app/index.yaml	Sat Jul 11 23:18:14 2009 +0200
@@ -56,6 +56,13 @@
   - name: status
   - name: __key__
 
+# used for bulk mailing student and mentors with survey reminders
+- kind: StudentProject
+  properties:
+  - name: program
+  - name: status
+  - name: __key__
+
 # AUTOGENERATED
 
 # This index.yaml is automatically updated whenever the dev_appserver
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/tasks/surveys.py	Sat Jul 11 23:18:14 2009 +0200
@@ -0,0 +1,264 @@
+#!/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.
+
+"""Tasks related to Surveys.
+"""
+
+__authors__ = [
+  '"Lennard de Rijk" <ljvderijk@gmail.com>',
+  ]
+
+
+import logging
+
+from google.appengine.api.labs import taskqueue
+
+from django import http
+
+
+def getDjangoURLPatterns():
+  """Returns the URL patterns for the tasks in this module.
+  """
+
+  patterns = [(r'tasks/surveys/projects/send_reminder/spawn$',
+               'soc.tasks.surveys.spawnRemindersForProjectSurvey'),
+              (r'tasks/surveys/projects/send_reminder/send$',
+               'soc.tasks.surveys.sendSurveyReminderForProject')]
+
+  return patterns
+
+
+def spawnRemindersForProjectSurvey(request, *args, **kwargs):
+  """Spawns tasks for each StudentProject in the given Program.
+
+  Expects the following to be present in the POST dict:
+    program_key: Specifies the program key name for which to loop over all the
+                 StudentProjects for
+    survey_key: specifies the key name for the ProjectSurvey to send reminders
+                for
+    survey_type: either project or grading depending on the type of Survey
+    project_key: optional to specifiy which project was the last for which a
+                 task was spawn
+
+  Args:
+    request: Django Request object
+  """
+  
+  from google.appengine.ext import db
+
+  from soc.logic.models.program import logic as program_logic
+  from soc.logic.models.student_project import logic as student_project_logic
+
+  # set default batch size
+  batch_size = 10
+
+  post_dict = request.POST
+
+  # retrieve the program_key and survey_key from POST data
+  program_key = post_dict.get('program_key')
+  survey_key = post_dict.get('survey_key')
+  survey_type = post_dict.get('survey_type')
+
+  if not (program_key and survey_key and survey_type):
+    # invalid task data, log and return OK
+    return logErrorAndReturnOK(
+        'Invalid sendRemindersForProjectSurvey data: %s' % post_dict)
+
+  # get the program for the given keyname
+  program_entity = program_logic.getFromKeyName(program_key)
+
+  if not program_entity:
+    # invalid program specified, log and return OK
+    return logErrorAndReturnOK('Invalid program specified: %s' % program_key)
+
+  # check and retrieve the project_key that has been done last
+  if 'project_key' in post_dict:
+    project_start_key = post_dict['project_key']
+  else:
+    project_start_key = None
+
+  # get all valid surveys from starting key
+  fields = {'program': program_entity,
+            'status': 'accepted'}
+
+  if project_start_key:
+    # retrieve the last project that was done
+    project_start = student_project_logic.getFromKeyName(project_start_key)
+
+    if not project_start:
+      # invalid starting project key specified, log and return OK
+      return logErrorAndReturnOK('Invalid Student Project Key specified: %s' %(
+          project_start_key))
+
+    fields['__key__ >'] = project_start.key()
+
+  project_entities = student_project_logic.getForFields(fields,
+                                                        limit=batch_size)
+
+  for project_entity in project_entities:
+    # pass along these params as POST to the new task
+    task_params = {'survey_key': survey_key,
+                   'survey_type': survey_type,
+                   'project_key': project_entity.key().id_or_name()}
+    task_url = '/tasks/surveys/projects/send_reminder/send'
+
+    new_task = taskqueue.Task(params=task_params, url=task_url)
+    new_task.add('mail')
+
+  if len(project_entities) == batch_size:
+    # spawn new task starting from the last
+    new_project_start = project_entities[batch_size-1].key().id_or_name()
+
+    # pass along these params as POST to the new task
+    task_params = {'program_key': program_key,
+                   'survey_key': survey_key,
+                   'survey_type': survey_type,
+                   'project_key': new_project_start}
+    task_url = '/tasks/surveys/projects/send_reminder/spawn'
+
+    new_task = taskqueue.Task(params=task_params, url=task_url)
+    new_task.add()
+
+  # return OK
+  return http.HttpResponse()
+
+
+def sendSurveyReminderForProject(request, *args, **kwargs):
+  """Sends a reminder mail for a given StudentProject and Survey.
+
+  A reminder is only send if no record is on file for the given Survey and 
+  StudentProject.
+
+  Expects the following to be present in the POST dict:
+    survey_key: specifies the key name for the ProjectSurvey to send reminders
+                for
+    survey_type: either project or grading depending on the type of Survey
+    project_key: key which specifies the project to send a reminder for
+
+  Args:
+    request: Django Request object
+  """
+
+  from soc.logic import mail_dispatcher
+  from soc.logic.models.org_admin import logic as org_admin_logic
+  from soc.logic.models.site import logic as site_logic
+  from soc.logic.models.student_project import logic as student_project_logic
+  from soc.logic.models.survey import grading_logic
+  from soc.logic.models.survey import project_logic
+  from soc.views.helper import redirects
+
+  post_dict = request.POST
+
+  project_key = post_dict.get('project_key')
+  survey_key = post_dict.get('survey_key')
+  survey_type = post_dict.get('survey_type')
+
+  if not (project_key and survey_key and survey_type):
+    # invalid task data, log and return OK
+    return logErrorAndReturnOK(
+        'Invalid sendSurveyReminderForProject data: %s' % post_dict)
+
+  # set logic depending on survey type specified in POST
+  if survey_type == 'project':
+    survey_logic = project_logic
+  elif survey_type == 'grading':
+    survey_logic = grading_logic
+
+  # retrieve the project and survey
+  student_project = student_project_logic.getFromKeyName(project_key)
+
+  if not student_project:
+    # no existing project found, log and return OK
+    return logErrorAndReturnOK('Invalid project specified %s:' % project_key)
+
+  survey = survey_logic.getFromKeyName(survey_key)
+
+  if not survey:
+    # no existing survey found, log and return OK
+    return logErrorAndReturnOK('Invalid survey specified %s:' % survey_key)
+
+  # try to retrieve an existing record
+  record_logic = survey_logic.getRecordLogic()
+
+  fields = {'project': student_project,
+            'survey': survey}
+  record_entity = record_logic.getForFields(fields, unique=True)
+
+  if not record_entity:
+    # send reminder email because we found no record
+
+    student_entity = student_project.student
+    site_entity = site_logic.getSingleton()
+
+    if survey_type == 'project':
+      survey_url = redirects.getTakeSurveyRedirect(
+          survey,{'url_name': 'project_survey'})
+      to_role = student_entity
+      mail_template = 'soc/project_survey/mail/reminder_gsoc.html'
+    elif survey_type == 'grading':
+      survey_url = redirects.getTakeSurveyRedirect(
+          survey,{'url_name': 'grading_project_survey'})
+      to_role = student_project.mentor
+      mail_template = 'soc/grading_project_survey/mail/reminder_gsoc.html'
+
+    # set the context for the mail template
+    mail_context = {
+        'student_name': student_entity.name(),
+        'project_title': student_project.title,
+        'survey_url': survey_url,
+        'survey_end': survey.survey_end,
+        'to_name': to_role.name(),
+        'site_name': site_entity.site_name,
+    }
+
+    # set the sender
+    (sender, sender_address) = mail_dispatcher.getDefaultMailSender()
+    mail_context['sender'] = sender_address
+    # set the receiver and subject
+    mail_context['to'] = to_role.email
+    mail_context['subject'] = 'Evaluation Survey "%s" Reminder' %(survey.title)
+
+    # find all org admins for the project's organization
+    org_entity = student_project.scope
+
+    fields = {'scope': org_entity,
+              'status': 'active'}
+    org_admin_entities = org_admin_logic.getForFields(fields)
+
+    # collect email addresses for all found org admins
+    org_admin_addresses = []
+
+    for org_admin_entity in org_admin_entities:
+      org_admin_addresses.append(org_admin_entity.email)
+
+    if org_admin_addresses:
+      mail_context['cc'] = org_admin_addresses
+
+    # send out the email
+    mail_dispatcher.sendMailFromTemplate(mail_template, mail_context)
+
+  # return OK
+  return http.HttpResponse()
+
+
+def logErrorAndReturnOK(error_msg='Error found in Survey Task'):
+  """Logs the given error message and returns a HTTP OK response.
+
+  Args:
+    error_msg: Error message to log
+  """
+  logging.error(error_msg)
+  return http.HttpResponse()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/templates/soc/grading_project_survey/mail/reminder_gsoc.html	Sat Jul 11 23:18:14 2009 +0200
@@ -0,0 +1,25 @@
+{% extends "soc/notification/messages/base.html" %}
+{% comment %}
+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.
+{% endcomment %}
+{% block content %}
+We've noticed that you have not yet completed the evaluation for your GSoC student {{ student_name }} (project title "{{ project_title }}"
+Please take a moment to visit {{ survey_url }} and complete the evaluation immediately.
+If you need assistance with the survey, please try asking for help on the Mentors Mailing list or in #gsoc on Freenode.
+Please note that we cannot make payment to your student until this evaluation is on file, so it's imperative that we receive it by the posted deadline of {{ survey_end|date:"jS F Y H:i" }} (UTC).
+{% endblock %}
+
+{% block signature %}
+Greetings, <br />
+The {{ site_name }} Team
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/templates/soc/project_survey/mail/reminder_gsoc.html	Sat Jul 11 23:18:14 2009 +0200
@@ -0,0 +1,25 @@
+{% extends "soc/notification/messages/base.html" %}
+{% comment %}
+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.
+{% endcomment %}
+{% block content %}
+We've noticed that you have not yet completed an evaluation for your GSoC project "{{ project_title }}".
+Please take a moment to visit {{ survey_url }} complete the evaluation immediately.
+If you need assistance with the survey, please try asking for help on the Students Mailing list or in #gsoc on Freenode.
+Please note that we cannot make payment to you until this evaluation is on file, so it's imperative that we receive it by the posted deadline of {{ survey_end|date:"jS F Y H:i" }} (UTC).
+{% endblock %}
+
+{% block signature %}
+Greetings, <br />
+The {{ site_name }} Team
+{% endblock %}