blob: 98403c90d01b3d7a3610da679d52f3d648c02041 [file] [log] [blame]
# Copyright 2010 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 Calculating duplicate proposals.
"""
import datetime
from google.appengine.api import taskqueue
from google.appengine.ext import db
from django import http
from django.conf.urls.defaults import url as django_url
from soc.tasks.helper import error_handler
from soc.modules.gsoc.models.program import GSoCProgram
from soc.modules.gsoc.models.organization import GSoCOrganization
from soc.modules.gsoc.logic import proposal as proposal_logic
from soc.modules.gsoc.logic import duplicates as duplicates_logic
from soc.modules.gsoc.models.proposal_duplicates import GSoCProposalDuplicate
# TODO(ljvderijk): General purpose task responses such as retry(), abort() and
# error message can be defined in a parent class of this object.
class ProposalDuplicatesTask(object):
"""Request handler for Proposal Duplicates view.
"""
def djangoURLPatterns(self):
"""Returns the URL patterns for the tasks in this module.
"""
patterns = [
django_url(r'^tasks/gsoc/proposal_duplicates/start$',
self.start, name='proposal_duplicates_task_start'),
django_url(r'^tasks/gsoc/proposal_duplicates/calculate$',
self.calculate, name='proposal_duplicates_task_calculate'),
]
return patterns
def start(self, request, *args, **kwargs):
"""Starts the task to find all duplicate proposals which are about to be
accepted for a single GSoCProgram.
Expects the following to be present in the POST dict:
program_key: Specifies the program key name for which to find the
duplicate proposals
repeat: Specifies if a new task that must be performed again an hour
later, with the same POST data
Args:
request: Django Request object
"""
from soc.logic.helper import timeline as timeline_helper
post_dict = request.POST
# retrieve the program_key and repeat option from POST data
program_key = post_dict.get('program_key')
repeat = post_dict.get('repeat')
if not (program_key and repeat):
# invalid task data, log and return OK
return error_handler.logErrorAndReturnOK(
'Invalid task data: %s' % post_dict)
# get the program for the given keyname
program_entity = GSoCProgram.get_by_key_name(program_key)
if not program_entity:
# invalid program specified, log and return OK
return error_handler.logErrorAndReturnOK(
'Invalid program specified: %s' % program_key)
# obtain the proposal duplicate status
pds_entity = duplicates_logic.getOrCreateStatusForProgram(program_entity)
if pds_entity.status == 'idle':
# delete all old duplicates
duplicates_logic.deleteAllForProgram(program_entity)
# pass these data along params as POST to the new task
task_params = {'program_key': program_key}
task_url = '/tasks/gsoc/proposal_duplicates/calculate'
new_task = taskqueue.Task(params=task_params, url=task_url)
def txn():
# add a new task that performs duplicate calculation per
# organization
new_task.add(transactional=True)
# update the status of the PDS entity to processing
pds_entity.status = 'processing'
pds_entity.put()
db.RunInTransaction(txn)
# Add a new clone of this task that must be performed an hour later because
# the current task is part of the task that repeatedly runs but repeat
# it before accepted students are announced only.
if repeat == 'yes' and timeline_helper.isBeforeEvent(
program_entity.timeline, 'accepted_students_announced_deadline'):
# pass along these params as POST to the new task
task_params = {'program_key': program_key,
'repeat': 'yes'}
task_url = '/tasks/gsoc/proposal_duplicates/start'
new_task = taskqueue.Task(params=task_params, url=task_url,
countdown=3600)
new_task.add()
# return OK
return http.HttpResponse()
def calculate(self, request, *args, **kwargs):
"""Calculates the duplicate proposals in a given program for
a student on a per Organization basis.
Expects the following to be present in the POST dict:
program_key: Specifies the program key name for which to find the
duplicate proposals
org_cursor: Specifies the organization datastore cursor from which to
start the processing of finding the duplicate proposals
Args:
request: Django Request object
"""
post_dict = request.POST
program_key = post_dict.get('program_key')
if not program_key:
# invalid task data, log and return OK
return error_handler.logErrorAndReturnOK(
'Invalid program key: %s' % post_dict)
program_entity = GSoCProgram.get_by_key_name(program_key)
if not program_entity:
# invalid program specified, log and return OK
return error_handler.logErrorAndReturnOK(
'Invalid program specified: %s' % program_key)
# get the organization and update the cursor if possible
q = GSoCOrganization.all()
q.filter('status', 'active')
q.filter('program', program_entity)
q.filter('slots >', 0)
# retrieve the org_cursor from POST data
org_cursor = post_dict.get('org_cursor')
if org_cursor:
org_cursor = str(org_cursor)
q.with_cursor(org_cursor)
org_entity = q.get()
# update the cursor
org_cursor = q.cursor()
if org_entity:
# get all the proposals likely to be accepted in the program
accepted_proposals = proposal_logic.getProposalsToBeAcceptedForOrg(org_entity)
for ap in accepted_proposals:
student_entity = ap.parent()
q = GSoCProposalDuplicate.all()
q.filter('student', student_entity)
proposal_duplicate = q.get()
if proposal_duplicate and ap.key() not in proposal_duplicate.duplicates:
# non-counted (to-be) accepted proposal found
proposal_duplicate.duplicates = proposal_duplicate.duplicates + \
[ap.key()]
proposal_duplicate.is_duplicate = \
len(proposal_duplicate.duplicates) >= 2
if org_entity.key() not in proposal_duplicate.orgs:
proposal_duplicate.orgs = proposal_duplicate.orgs + [org_entity.key()]
else:
pd_fields = {
'program': program_entity,
'student': student_entity,
'orgs':[org_entity.key()],
'duplicates': [ap.key()],
'is_duplicate': False
}
proposal_duplicate = GSoCProposalDuplicate(**pd_fields)
proposal_duplicate.put()
# Adds a new task that performs duplicate calculation for
# the next organization.
task_params = {'program_key': program_key,
'org_cursor': unicode(org_cursor)}
task_url = '/tasks/gsoc/proposal_duplicates/calculate'
new_task = taskqueue.Task(params=task_params, url=task_url)
new_task.add()
else:
# There aren't any more organizations to process. So delete
# all the proposals for which there are not more than one
# proposal for duplicates property.
duplicates_logic.deleteAllForProgram(program_entity, non_dupes_only=True)
# update the proposal duplicate status and its timestamp
pds_entity = duplicates_logic.getOrCreateStatusForProgram(program_entity)
pds_entity.status = 'idle'
pds_entity.calculated_on = datetime.datetime.now()
pds_entity.put()
# return OK
return http.HttpResponse()