blob: 5df1feb3186bdf054e295faff95125f83f17e6ac [file] [log] [blame]
#!/usr/bin/env python2.5
#
# Copyright 2011 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.
"""The proposal conversion updates are defined in this module.
"""
import gae_django
from google.appengine.ext import db
from google.appengine.ext import deferred
from google.appengine.runtime import DeadlineExceededError
from django import http
from soc.modules.gsoc.models.comment import GSoCComment
from soc.modules.gsoc.models.profile import GSoCProfile
from soc.modules.gsoc.models.proposal import GSoCProposal
from soc.modules.gsoc.models.review import Review
from soc.modules.gsoc.models.student_proposal import StudentProposal
def getDjangoURLPatterns():
"""Returns the URL patterns for the tasks in this module.
"""
patterns = [
(r'^tasks/proposal_conversion/update_proposals',
'soc.tasks.updates.proposal_conversion.updateProposals'),
]
return patterns
class ProposalUpdater(object):
"""Class which is responsible for updating the entities.
"""
def run(self, batch_size=25):
"""Starts the updater.
"""
self._process(None, batch_size)
def _processEntity(self, entity):
key_name = entity.key().name()
parent = entity.scope
properties = {
'abstract': entity.abstract,
'additional_info': entity.additional_info,
'content': entity.content,
'created_on': entity.created_on,
'is_publicly_visible': entity.is_publicly_visible,
'last_modified_on': entity.last_modified_on,
'mentor': entity.mentor,
'org': entity.org,
'possible_mentors': entity.possible_mentors,
'program': entity.program,
'status': entity.status,
'title': entity.title,
}
# check if the proposal has already been processed
# this is a heristic, but we can assume that one student can't submit two
# proposals at the very same time
query = db.Query(GSoCProposal)
query.ancestor(entity.scope)
query.filter('created_on = ', entity.created_on)
if query.get():
return
# create a new GSoCProposal entity
proposal = GSoCProposal(parent=parent, **properties)
proposal.put()
to_put = []
# convert all the comments for the old proposal
query = db.Query(Review)
query.filter('scope = ', entity)
for comment in query:
# get profile instance
q = db.Query(GSoCProfile)
q.ancestor(comment.author)
q.filter('scope =', entity.program)
author = q.get()
if not author:
# if, for some reason, there is no profile, we skip this comment
import logging
logging.warning('No profile for user %s.' % (comment.author.link_id))
continue
properties = {
'author': author,
'content': comment.content,
'is_private': not comment.is_public,
'created': comment.created
}
new_comment = GSoCComment(parent=proposal, **properties)
to_put.append(new_comment)
db.run_in_transaction(db.put, to_put)
def _process(self, start_key, batch_size):
"""Retrieves entities and creates or updates a corresponding
Profile entity.
"""
query = StudentProposal.all()
if start_key:
query.filter('__key__ > ', start_key)
try:
entities = query.fetch(batch_size)
if not entities:
# all entities has already been processed
return
for entity in entities:
try:
self._processEntity(entity)
except db.Error, e:
import logging
logging.exception(e)
logging.error("Broke on %s: StudentProposal" % (entity.key().name()))
# process the next batch of entities
start_key = entities[-1].key()
deferred.defer(self._process, start_key, batch_size)
except DeadlineExceededError:
# here we should probably be more careful
deferred.defer(self._process, start_key, batch_size)
def updateProposals(request):
"""Starts a task which updates proposals.
"""
updater = ProposalUpdater()
updater.run()
return http.HttpResponse("Ok")