--- a/app/soc/modules/ghop/logic/models/task.py Thu Aug 27 22:40:26 2009 +0530
+++ b/app/soc/modules/ghop/logic/models/task.py Fri Aug 28 12:34:16 2009 +0200
@@ -18,11 +18,19 @@
"""
__authors__ = [
- '"Madhusudan.C.S" <madhusudancs@gmail.com>'
+ '"Madhusudan.C.S" <madhusudancs@gmail.com>',
+ '"Lennard de Rijk" <ljvderijk@gmail.com>',
]
-from soc.logic.models import linkable
+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
@@ -30,10 +38,32 @@
import soc.modules.ghop.models.task
-class Logic(linkable.Logic):
+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):
@@ -43,5 +73,359 @@
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, siltent=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 = 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()