Add task and iterative_task decorator
Also add getBatchOfData method to the base logic and tasks logic
module.
Reviewed-by: Sverre Rabbelier
#!/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.
"""GHOPTask (Model) query functions.
"""
__authors__ = [
'"Madhusudan.C.S" <madhusudancs@gmail.com>',
'"Lennard de Rijk" <ljvderijk@gmail.com>',
]
import datetime
from google.appengine.ext import db
from django.utils import simplejson
from django.utils.translation import ugettext
from soc.logic.models import base
import soc.models.linkable
import soc.modules.ghop.logic.models.organization
import soc.modules.ghop.models.task
STATE_TRANSITIONS = {
'Claimed': 'transitFromClaimed',
'NeedsReview': 'transitFromNeedsReview',
'ActionNeeded': 'transitFromActionNeeded',
'NeedsWork': 'transitFromNeedsWork',
}
class Logic(base.Logic):
"""Logic methods for the GHOPTask model.
"""
DEF_ACTION_NEEDED_MSG = ugettext(
'(The Melange Automated System has detected that the intial '
'deadline has been passed and it has set the task status to '
'ActionNeeded.)')
DEF_NO_MORE_WORK_MSG = ugettext(
'(The Melange Automated System has detected that the deadline '
'has been passed and no more work can be submitted.)')
DEF_REOPENED_MSG = ugettext(
'(The Melange Automated System has detected that the final '
'deadline has been passed and it has Reopened the task.)')
def __init__(self, model=soc.modules.ghop.models.task.GHOPTask,
base_model=soc.models.linkable.Linkable,
scope_logic=soc.modules.ghop.logic.models.organization):
"""Defines the name, key_name and model for this entity.
"""
super(Logic, self).__init__(model, base_model=base_model,
scope_logic=scope_logic)
def updateEntityProperties(self, entity, entity_properties,
silent=False, store=True):
"""See base.Logic.updateEntityProperties().
Also ensures that the history property of the task is updated in the same
datastore operation.
"""
from soc.logic.models import base as base_logic
# TODO: History needs a proper test drive and perhaps a refactoring
history = {}
# we construct initial snapshot of the task when it is published
# for the first time.
if entity_properties and 'status' in entity_properties:
if entity.status == 'Unpublished' or entity.status == 'Unapproved':
if entity_properties['status'] == 'Open':
history = {
'title': entity.title,
'description': entity.description,
'difficulty': entity.difficulty[0].tag,
'task_type': [type.tag for type in entity.task_type],
'time_to_complete': entity.time_to_complete,
'mentors': [m_key.name() for m_key in entity.mentors],
'user': '',
'student': '',
'status': entity.status,
'deadline': '',
'created_by': entity.created_by.key().name(),
'created_on': str(entity.created_on),
'modified_on': str(entity.modified_on),
}
if entity.modified_by:
history['modified_by'] = entity.modified_by.key().name()
# initialize history
task_history = {}
# extract the existing json history from the entity to update it
else:
task_history = simplejson.loads(entity.history)
# we construct history for only changed entity properties
if entity_properties:
for property in entity_properties:
changed_val = getattr(entity, property)
if changed_val != entity_properties[property]:
if property == 'deadline':
history[property] = str(changed_val)
else:
history[property] = changed_val
if history:
# create a dictionary for the new history update with timestamp as key
tstamp = str(datetime.datetime.now())
new_history = {tstamp: history}
# update existing history
task_history.update(new_history)
task_history_str = simplejson.dumps(task_history)
# update the task's history property
history_property = {
'history': task_history_str
}
entity_properties.update(history_property)
# call the base logic method to store the updated Task entity
return super(Logic, self).updateEntityProperties(
entity, entity_properties, silent=silent, store=store)
def updateEntityPropertiesWithCWS(self, entity, entity_properties,
comment_properties=None,
ws_properties=None, silent=False):
"""Updates the GHOPTask entity properties and creates a comment
entity.
Args:
entity: a model entity
entity_properties: keyword arguments that correspond to entity
properties and their values
comment_properties: keyword arguments that correspond to the
GHOPTask's to be created comment entity
silent: iff True does not call post store methods.
"""
from soc.modules.ghop.logic.models import comment as ghop_comment_logic
from soc.modules.ghop.logic.models import work_submission as \
ghop_work_submission_logic
from soc.modules.ghop.models import comment as ghop_comment_model
from soc.modules.ghop.models import work_submission as \
ghop_work_submission_model
if entity_properties:
entity = self.updateEntityProperties(entity, entity_properties,
silent=silent, store=False)
comment_entity = ghop_comment_model.GHOPComment(**comment_properties)
ws_entity = None
if ws_properties:
ws_entity = ghop_work_submission_model.GHOPWorkSubmission(
**ws_properties)
def comment_create():
"""Method to be run in transaction that stores Task, Comment and
WorkSubmission.
"""
entity.put()
if ws_entity:
ws_entity.put()
comment_entity.content = comment_entity.content % (
ws_entity.key().id_or_name())
comment_entity.put()
return entity, comment_entity, ws_entity
else:
comment_entity.put()
return entity, comment_entity, None
entity, comment_entity, ws_entity = db.run_in_transaction(
comment_create)
if not silent:
# call the _onCreate methods for the Comment and WorkSubmission
if comment_entity:
ghop_comment_logic.logic._onCreate(comment_entity)
if ws_entity:
ghop_work_submission_logic._onCreate(ws_entity)
return entity, comment_entity, ws_entity
def updateOrCreateFromFields(self, properties, silent=False):
"""See base.Logic.updateOrCreateFromFields().
"""
# TODO: History needs to be tested and perhaps refactored
if properties['status'] == 'Open':
history = {
'title': properties['title'],
'description': properties['description'],
'difficulty': properties['difficulty']['tags'],
'task_type': properties['type_tags'],
'time_to_complete': properties['time_to_complete'],
'mentors': [m_key.name() for m_key in properties['mentors']],
'user': '',
'student': '',
'status': properties['status'],
'deadline': '',
'created_on': str(properties['created_on']),
'modified_on': str(properties['modified_on']),
}
if 'created_by' in properties and properties['created_by']:
history['created_by'] = properties['created_by'].key().name()
history['modified_by'] = properties['modified_by'].key().name()
# Constructs new history from the _constructNewHistory method, assigns
# it as a value to the dictionary key with current timestamp and dumps
# a JSON string.
task_history_str = simplejson.dumps({
str(datetime.datetime.now()): history,
})
# update the task's history property
history_property = {
'history': task_history_str
}
properties.update(history_property)
entity = super(Logic, self).updateOrCreateFromFields(properties, silent)
if entity:
if properties.get('task_type'):
setattr(entity, 'task_type', properties['task_type'])
if properties.get('difficulty'):
setattr(entity, 'difficulty', properties['difficulty'])
return entity
def getFromKeyFieldsWithCWSOr404(self, fields):
"""Returns the Task, all Comments and all WorkSubmissions for the Task
specified by the fields argument.
For args see base.getFromKeyFieldsOr404().
"""
from soc.modules.ghop.logic.models import comment as ghop_comment_logic
from soc.modules.ghop.logic.models import work_submission as \
ghop_work_submission_logic
entity = self.getFromKeyFieldsOr404(fields)
comment_entities = ghop_comment_logic.logic.getForFields(
ancestors=[entity], order=['created_on'])
ws_entities = ghop_work_submission_logic.logic.getForFields(
ancestors=[entity], order=['submitted_on'])
return entity, comment_entities, ws_entities
def updateTaskStatus(self, entity):
"""Method used to transit a task from a state to another state
depending on the context. Whenever the deadline has passed.
Args:
entity: The GHOPTask entity
Returns:
Task entity and a Comment entity if the occurring transit created one.
"""
from soc.modules.ghop.tasks import task_update
if entity.deadline and datetime.datetime.now() > entity.deadline:
# calls a specific method to make a transition depending on the
# task's current state
transit_func = getattr(self, STATE_TRANSITIONS[entity.status])
update_dict = transit_func(entity)
comment_properties = {
'parent': entity,
'scope_path': entity.key().name(),
'created_by': None,
'content': update_dict['content'],
'changes': update_dict['changes'],
}
entity, comment_entity, _ = self.updateEntityPropertiesWithCWS(
entity, update_dict['properties'], comment_properties)
if entity.deadline:
# only if there is a deadline set we should schedule another task
task_update.spawnUpdateTask(entity)
else:
comment_entity=None
return entity, comment_entity
def transitFromClaimed(self, entity):
"""Makes a state transition of a GHOP Task from Claimed state
to a relevant state.
Args:
entity: The GHOPTask entity
"""
# deadline is extended by 24 hours.
deadline = entity.deadline + datetime.timedelta(
hours=24)
properties = {
'status': 'ActionNeeded',
'deadline': deadline,
}
changes = [ugettext('User-MelangeAutomatic'),
ugettext('Action-Warned for action'),
ugettext('Status-%s' % (properties['status']))]
content = self.DEF_ACTION_NEEDED_MSG
update_dict = {
'properties': properties,
'changes': changes,
'content': content,
}
return update_dict
def transitFromNeedsReview(self, entity):
"""Makes a state transition of a GHOP Task from NeedsReview state
to a relevant state.
Args:
entity: The GHOPTask entity
"""
properties = {
'deadline': None,
}
changes = [ugettext('User-MelangeAutomatic'),
ugettext('Action-Deadline passed'),
ugettext('Status-%s' % (entity.status))]
content = self.DEF_NO_MORE_WORK_MSG
update_dict = {
'properties': properties,
'changes': changes,
'content': content,
}
return update_dict
def transitFromActionNeeded(self, entity):
"""Makes a state transition of a GHOP Task from ActionNeeded state
to a relevant state.
Args:
entity: The GHOPTask entity
"""
properties = {
'user': None,
'student': None,
'status': 'Reopened',
'deadline': None,
}
changes = [ugettext('User-MelangeAutomatic'),
ugettext('Action-Forcibly reopened'),
ugettext('Status-Reopened')]
content = self.DEF_REOPENED_MSG
update_dict = {
'properties': properties,
'changes': changes,
'content': content,
}
return update_dict
def transitFromNeedsWork(self, entity):
"""Makes a state transition of a GHOP Task from NeedsWork state
to a relevant state.
Args:
entity: The GHOPTask entity
"""
properties = {
'user': None,
'student': None,
'status': 'Reopened',
'deadline': None,
}
changes = [ugettext('User-MelangeAutomatic'),
ugettext('Action-Forcibly reopened'),
ugettext('Status-Reopened')]
update_dict = {
'properties': properties,
'changes': changes,
'content': None,
}
return update_dict
logic = Logic()