app/soc/modules/ghop/logic/models/task.py
changeset 2823 21c222535654
parent 2407 e23fce20ad3a
child 2831 a7ed56911653
equal deleted inserted replaced
2822:387a3b80df05 2823:21c222535654
    16 
    16 
    17 """GHOPTask (Model) query functions.
    17 """GHOPTask (Model) query functions.
    18 """
    18 """
    19 
    19 
    20 __authors__ = [
    20 __authors__ = [
    21     '"Madhusudan.C.S" <madhusudancs@gmail.com>'
    21     '"Madhusudan.C.S" <madhusudancs@gmail.com>',
       
    22     '"Lennard de Rijk" <ljvderijk@gmail.com>',
    22   ]
    23   ]
    23 
    24 
    24 
    25 
    25 from soc.logic.models import linkable
    26 import datetime
       
    27 
       
    28 from google.appengine.ext import db
       
    29 
       
    30 from django.utils import simplejson
       
    31 from django.utils.translation import ugettext
       
    32 
       
    33 from soc.logic.models import base
    26 
    34 
    27 import soc.models.linkable
    35 import soc.models.linkable
    28 
    36 
    29 import soc.modules.ghop.logic.models.organization
    37 import soc.modules.ghop.logic.models.organization
    30 import soc.modules.ghop.models.task
    38 import soc.modules.ghop.models.task
    31 
    39 
    32 
    40 
    33 class Logic(linkable.Logic):
    41 STATE_TRANSITIONS = {
       
    42     'Claimed': transitFromClaimed,
       
    43     'NeedsReview': transitFromNeedsReview,
       
    44     'ActionNeeded': transitFromActionNeeded,
       
    45     'NeedsWork': transitFromNeedsWork,
       
    46     }
       
    47 
       
    48 
       
    49 class Logic(base.Logic):
    34   """Logic methods for the GHOPTask model.
    50   """Logic methods for the GHOPTask model.
    35   """
    51   """
       
    52 
       
    53   DEF_ACTION_NEEDED_MSG = ugettext(
       
    54       '(The Melange Automated System has detected that the intial '
       
    55       'deadline has been passed and it has set the task status to '
       
    56       'ActionNeeded.)')
       
    57 
       
    58   DEF_NO_MORE_WORK_MSG = ugettext(
       
    59       '(The Melange Automated System has detected that the deadline '
       
    60       'has been passed and no more work can be submitted.)')
       
    61 
       
    62   DEF_REOPENED_MSG = ugettext(
       
    63       '(The Melange Automated System has detected that the final '
       
    64       'deadline has been passed and it has Reopened the task.)')
       
    65 
    36 
    66 
    37   def __init__(self, model=soc.modules.ghop.models.task.GHOPTask,
    67   def __init__(self, model=soc.modules.ghop.models.task.GHOPTask,
    38                base_model=soc.models.linkable.Linkable, 
    68                base_model=soc.models.linkable.Linkable, 
    39                scope_logic=soc.modules.ghop.logic.models.organization):
    69                scope_logic=soc.modules.ghop.logic.models.organization):
    40     """Defines the name, key_name and model for this entity.
    70     """Defines the name, key_name and model for this entity.
    41     """
    71     """
    42 
    72 
    43     super(Logic, self).__init__(model, base_model=base_model,
    73     super(Logic, self).__init__(model, base_model=base_model,
    44                                 scope_logic=scope_logic)
    74                                 scope_logic=scope_logic)
    45 
    75 
       
    76   def updateEntityProperties(self, entity, entity_properties,
       
    77                              silent=False, store=True):
       
    78     """See base.Logic.updateEntityProperties().
       
    79 
       
    80     Also ensures that the history property of the task is updated in the same
       
    81     datastore operation.
       
    82     """
       
    83 
       
    84     from soc.logic.models import base as base_logic
       
    85 
       
    86     # TODO: History needs a proper test drive and perhaps a refactoring
       
    87     history = {}
       
    88 
       
    89     # we construct initial snapshot of the task when it is published
       
    90     # for the first time.
       
    91     if entity_properties and 'status' in entity_properties:
       
    92       if entity.status == 'Unpublished' or entity.status == 'Unapproved':
       
    93         if entity_properties['status'] == 'Open':
       
    94           history = {
       
    95               'title': entity.title,
       
    96               'description': entity.description,
       
    97               'difficulty': entity.difficulty[0].tag,
       
    98               'task_type': [type.tag for type in entity.task_type],
       
    99               'time_to_complete': entity.time_to_complete,
       
   100               'mentors': [m_key.name() for m_key in entity.mentors],
       
   101               'user': '',
       
   102               'student': '',
       
   103               'status': entity.status,
       
   104               'deadline': '',
       
   105               'created_by': entity.created_by.key().name(),
       
   106               'created_on': str(entity.created_on),
       
   107               'modified_on': str(entity.modified_on),
       
   108               }
       
   109 
       
   110           if entity.modified_by:
       
   111             history['modified_by'] = entity.modified_by.key().name()
       
   112 
       
   113           # initialize history
       
   114           task_history = {}
       
   115       # extract the existing json history from the entity to update it
       
   116     else:
       
   117       task_history = simplejson.loads(entity.history)
       
   118 
       
   119       # we construct history for only changed entity properties
       
   120       if entity_properties:
       
   121         for property in entity_properties:
       
   122           changed_val = getattr(entity, property)
       
   123           if changed_val != entity_properties[property]:
       
   124             if property == 'deadline':
       
   125               history[property] = str(changed_val)
       
   126             else:
       
   127               history[property] = changed_val
       
   128 
       
   129     if history:
       
   130       # create a dictionary for the new history update with timestamp as key
       
   131       tstamp = str(datetime.datetime.now())
       
   132       new_history = {tstamp: history}
       
   133 
       
   134       # update existing history
       
   135       task_history.update(new_history)
       
   136       task_history_str = simplejson.dumps(task_history)
       
   137 
       
   138       # update the task's history property
       
   139       history_property = {
       
   140           'history': task_history_str
       
   141           }
       
   142       entity_properties.update(history_property)
       
   143 
       
   144       # call the base logic method to store the updated Task entity
       
   145       return super(Logic, self).updateEntityProperties(
       
   146           entity, entity_properties, siltent=silent, store=store)
       
   147 
       
   148   def updateEntityPropertiesWithCWS(self, entity, entity_properties,
       
   149                                     comment_properties=None, 
       
   150                                     ws_properties=None, silent=False):
       
   151     """Updates the GHOPTask entity properties and creates a comment
       
   152     entity.
       
   153 
       
   154     Args:
       
   155       entity: a model entity
       
   156       entity_properties: keyword arguments that correspond to entity
       
   157           properties and their values
       
   158       comment_properties: keyword arguments that correspond to the
       
   159           GHOPTask's to be created comment entity
       
   160       silent: iff True does not call post store methods.
       
   161     """
       
   162 
       
   163     from soc.modules.ghop.logic.models import comment as ghop_comment_logic
       
   164     from soc.modules.ghop.logic.models import work_submission as \
       
   165         ghop_work_submission_logic
       
   166     from soc.modules.ghop.models import comment as ghop_comment_model
       
   167     from soc.modules.ghop.models import work_submission as \
       
   168         ghop_work_submission_model
       
   169 
       
   170     if entity_properties:
       
   171       entity = self.updateEntityProperties(entity, entity_properties, 
       
   172                                            silent=silent, store=False)
       
   173 
       
   174     comment_entity = ghop_comment_model.GHOPComment(**comment_properties)
       
   175 
       
   176     ws_entity = None
       
   177     if ws_properties:
       
   178       ws_entity = ghop_work_submission_model.GHOPWorkSubmission(
       
   179           **ws_properties)
       
   180 
       
   181     def comment_create():
       
   182       """Method to be run in transaction that stores Task, Comment and
       
   183       WorkSubmission.
       
   184       """
       
   185       entity.put()
       
   186       if ws_entity:
       
   187         ws_entity.put()
       
   188         comment_entity.content = comment_entity.content % (
       
   189             ws_entity.key().id_or_name())
       
   190         comment_entity.put()
       
   191         return entity, comment_entity, ws_entity
       
   192       else:
       
   193         comment_entity.put()
       
   194         return entity, comment_entity, None
       
   195 
       
   196     entity, comment_entity, ws_entity = db.run_in_transaction(
       
   197         comment_create)
       
   198 
       
   199     if not silent:
       
   200       # call the _onCreate methods for the Comment and WorkSubmission
       
   201       if comment_entity:
       
   202         ghop_comment_logic.logic._onCreate(comment_entity)
       
   203 
       
   204       if ws_entity:
       
   205         ghop_work_submission_logic._onCreate(ws_entity)
       
   206 
       
   207     return entity, comment_entity, ws_entity
       
   208 
       
   209   def updateOrCreateFromFields(self, properties, silent=False):
       
   210     """See base.Logic.updateOrCreateFromFields().
       
   211     """
       
   212 
       
   213     # TODO: History needs to be tested and perhaps refactored
       
   214     if properties['status'] == 'Open':
       
   215       history = {
       
   216           'title': properties['title'],
       
   217           'description': properties['description'],
       
   218           'difficulty': properties['difficulty']['tags'],
       
   219           'task_type': properties['type_tags'],
       
   220           'time_to_complete': properties['time_to_complete'],
       
   221           'mentors': [m_key.name() for m_key in properties['mentors']],
       
   222           'user': '',
       
   223           'student': '',
       
   224           'status': properties['status'],
       
   225           'deadline': '',
       
   226           'created_on': str(properties['created_on']),
       
   227           'modified_on': str(properties['modified_on']),
       
   228           }
       
   229 
       
   230       if 'created_by' in properties and properties['created_by']:
       
   231         history['created_by'] = properties['created_by'].key().name()
       
   232         history['modified_by'] = properties['modified_by'].key().name()
       
   233 
       
   234       # Constructs new history from the _constructNewHistory method, assigns
       
   235       # it as a value to the dictionary key with current timestamp and dumps
       
   236       # a JSON string.
       
   237       task_history_str = simplejson.dumps({
       
   238           str(datetime.datetime.now()): history,
       
   239           })
       
   240 
       
   241       # update the task's history property
       
   242       history_property = {
       
   243           'history': task_history_str
       
   244           }
       
   245       properties.update(history_property)
       
   246 
       
   247     entity = super(Logic, self).updateOrCreateFromFields(properties, silent)
       
   248 
       
   249     if entity:
       
   250       if properties.get('task_type'):
       
   251         setattr(entity, 'task_type', properties['task_type'])
       
   252 
       
   253       if properties.get('difficulty'):
       
   254         setattr(entity, 'difficulty', properties['difficulty'])
       
   255 
       
   256     return entity
       
   257 
       
   258   def getFromKeyFieldsWithCWSOr404(self, fields):
       
   259     """Returns the Task, all Comments and all WorkSubmissions for the Task
       
   260     specified by the fields argument.
       
   261 
       
   262     For args see base.getFromKeyFieldsOr404().
       
   263     """
       
   264 
       
   265     from soc.modules.ghop.logic.models import comment as ghop_comment_logic
       
   266     from soc.modules.ghop.logic.models import work_submission as \
       
   267         ghop_work_submission_logic
       
   268  
       
   269     entity = self.getFromKeyFieldsOr404(fields)
       
   270 
       
   271     comment_entities = ghop_comment_logic.logic.getForFields(
       
   272         ancestors=[entity], order=['created_on'])
       
   273 
       
   274     ws_entities = ghop_work_submission_logic.logic.getForFields(
       
   275         ancestors=[entity], order=['submitted_on'])
       
   276 
       
   277     return entity, comment_entities, ws_entities
       
   278 
       
   279   def updateTaskStatus(self, entity):
       
   280     """Method used to transit a task from a state to another state
       
   281     depending on the context. Whenever the deadline has passed.
       
   282 
       
   283     Args:
       
   284       entity: The GHOPTask entity
       
   285 
       
   286     Returns:
       
   287       Task entity and a Comment entity if the occurring transit created one.
       
   288     """
       
   289 
       
   290     from soc.modules.ghop.tasks import task_update
       
   291 
       
   292     if entity.deadline and datetime.datetime.now() > entity.deadline:
       
   293       # calls a specific method to make a transition depending on the
       
   294       # task's current state
       
   295       transit_func = STATE_TRANSITIONS[entity.status]
       
   296       update_dict = transit_func(entity)
       
   297 
       
   298       comment_properties = {
       
   299           'parent': entity,
       
   300           'scope_path': entity.key().name(),
       
   301           'created_by': None,
       
   302           'content': update_dict['content'],
       
   303           'changes': update_dict['changes'],
       
   304           }
       
   305 
       
   306       entity, comment_entity, _ = self.updateEntityPropertiesWithCWS(
       
   307           entity, update_dict['properties'], comment_properties)
       
   308 
       
   309       if entity.deadline:
       
   310         # only if there is a deadline set we should schedule another task
       
   311         task_update.spawnUpdateTask(entity)
       
   312     else:
       
   313       comment_entity=None
       
   314 
       
   315     return entity, comment_entity
       
   316 
       
   317   def transitFromClaimed(self, entity):
       
   318     """Makes a state transition of a GHOP Task from Claimed state
       
   319     to a relevant state.
       
   320 
       
   321     Args:
       
   322       entity: The GHOPTask entity
       
   323     """
       
   324 
       
   325     # deadline is extended by 24 hours.
       
   326     deadline = entity.deadline + datetime.timedelta(
       
   327         hours=24)
       
   328 
       
   329     properties = {
       
   330         'status': 'ActionNeeded',
       
   331         'deadline': deadline,
       
   332         }
       
   333 
       
   334     changes = [ugettext('User-MelangeAutomatic'),
       
   335                ugettext('Action-Warned for action'),
       
   336                ugettext('Status-%s' % (properties['status']))]
       
   337 
       
   338     content = self.DEF_ACTION_NEEDED_MSG
       
   339 
       
   340     update_dict = {
       
   341         'properties': properties,
       
   342         'changes': changes,
       
   343         'content': content,
       
   344         }
       
   345 
       
   346     return update_dict
       
   347 
       
   348   def transitFromNeedsReview(self, entity):
       
   349     """Makes a state transition of a GHOP Task from NeedsReview state
       
   350     to a relevant state.
       
   351 
       
   352     Args:
       
   353       entity: The GHOPTask entity
       
   354     """
       
   355 
       
   356     properties = {
       
   357         'deadline': None,
       
   358         }
       
   359 
       
   360     changes = [ugettext('User-MelangeAutomatic'),
       
   361                ugettext('Action-Deadline passed'),
       
   362                ugettext('Status-%s' % (entity.status))]
       
   363 
       
   364     content = self.DEF_NO_MORE_WORK_MSG
       
   365 
       
   366     update_dict = {
       
   367         'properties': properties,
       
   368         'changes': changes,
       
   369         'content': content,
       
   370         }
       
   371 
       
   372     return update_dict
       
   373 
       
   374   def transitFromActionNeeded(self, entity):
       
   375     """Makes a state transition of a GHOP Task from ActionNeeded state
       
   376     to a relevant state.
       
   377 
       
   378     Args:
       
   379       entity: The GHOPTask entity
       
   380     """
       
   381 
       
   382     properties = {
       
   383         'user': None,
       
   384         'student': None,
       
   385         'status': 'Reopened',
       
   386         'deadline': None,
       
   387         }
       
   388 
       
   389     changes = [ugettext('User-MelangeAutomatic'),
       
   390                ugettext('Action-Forcibly reopened'),
       
   391                ugettext('Status-Reopened')]
       
   392 
       
   393     content = self.DEF_REOPENED_MSG
       
   394 
       
   395     update_dict = {
       
   396         'properties': properties,
       
   397         'changes': changes,
       
   398         'content': content,
       
   399         }
       
   400 
       
   401     return update_dict
       
   402 
       
   403   def transitFromNeedsWork(self, entity):
       
   404     """Makes a state transition of a GHOP Task from NeedsWork state
       
   405     to a relevant state.
       
   406 
       
   407     Args:
       
   408       entity: The GHOPTask entity
       
   409     """
       
   410 
       
   411     properties = {
       
   412         'user': None,
       
   413         'student': None,
       
   414         'status': 'Reopened',
       
   415         'deadline': None,
       
   416         }
       
   417 
       
   418     changes = [ugettext('User-MelangeAutomatic'),
       
   419                ugettext('Action-Forcibly reopened'),
       
   420                ugettext('Status-Reopened')]
       
   421     
       
   422     update_dict = {
       
   423         'properties': properties,
       
   424         'changes': changes,
       
   425         'content': None,
       
   426         }
       
   427 
       
   428     return update_dict
       
   429 
    46 
   430 
    47 logic = Logic()
   431 logic = Logic()