def claimRequestTask(task, student): """Used when a student requests to claim a task. Updates the status of the tasks and places a comment notifying the org that someone wants to work on this task. Args: task: The task to claim. student: Profile of the student that wants to claim the task. """ task.status = 'ClaimRequested' task.student = student.key.to_old_key() if student.key.to_old_key() not in task.subscribers: task.subscribers.append(student.key.to_old_key()) comment_props = { 'parent': task, 'title': DEF_CLAIM_REQUEST_TITLE, 'content': DEF_CLAIM_REQUEST, 'created_by': student.key.parent().to_old_key() } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def claimRequestTaskTxn(): task.put() comment_txn() return db.run_in_transaction(claimRequestTaskTxn)
def unpublishTask(task, unpublisher): """Unpublishes the task. This will put the task in the Unpublished state. A comment will also be generated to record this event. Args: task: GCITask entity. publisher: GCIProfile of the user that unpublishes the task. """ task.status = task_model.UNPUBLISHED comment_props = { 'parent': task, 'title': DEF_UNPUBLISHED_TITLE, 'content': DEF_UNPUBLISHED, 'created_by': unpublisher.key.parent().to_old_key(), } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def unpublishTaskTxn(): task.put() comment_txn() _spawnUpdateTask(task, transactional=True) return db.run_in_transaction(unpublishTaskTxn)
def unassignTask(task, profile): """Unassigns a task. This will put the task in the Reopened state and reset the student and deadline property. A comment will also be generated to record this event. Args: task: task_model.GCITask entity. profile: GCIProfile of the user that unassigns the task. """ task.student = None task.status = task_model.REOPENED task.deadline = None comment_props = { 'parent': task, 'title': DEF_UNASSIGNED_TITLE, 'content': DEF_UNASSIGNED, 'created_by': profile.key.parent().to_old_key() } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def unassignTaskTxn(): task.put() comment_txn() return db.run_in_transaction(unassignTaskTxn)
def assignTask(task, student_key, assigner): """Assigns the task to the student. This will put the task in the Claimed state and set the student and deadline property. A comment will also be generated to record this event. Args: task: task_model.GCITask entity. student_key: Key of the student to assign assigner: GCIProfile of the user that assigns the student. """ task.student = student_key.to_old_key() task.status = 'Claimed' task.deadline = datetime.datetime.now() + \ datetime.timedelta(hours=task.time_to_complete) student = student_key.get() comment_props = { 'parent': task, 'title': DEF_ASSIGNED_TITLE, 'content': DEF_ASSIGNED %( student.public_name, task.time_to_complete), 'created_by': assigner.key.parent().to_old_key(), } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def assignTaskTxn(): task.put() comment_txn() _spawnUpdateTask(task, transactional=True) return db.run_in_transaction(assignTaskTxn)
def extendDeadline(task, delta, profile): """Extends the deadline of a task. Args: task: The task to extend the deadline for. delta: The timedelta object to be added to the current deadline. profile: GCIProfile of the user that extends the deadline. """ if task.deadline: deadline = task.deadline + delta else: deadline = datetime.datetime.utcnow() + delta task.deadline = deadline comment_props = { 'parent': task, 'title': DEF_EXTEND_DEADLINE_TITLE, 'content': DEF_EXTEND_DEADLINE %(delta.days, delta.seconds/3600), 'created_by': profile.key.parent().to_old_key() } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def extendDeadlineTxn(): task.put() comment_txn() return db.run_in_transaction(extendDeadlineTxn)
def unclaimTask(task): """Used when a student requests to unclaim a task. Args: task: The task to unclaim. """ student_key = task_model.GCITask.student.get_value_for_datastore(task) task.student = None task.status = task_model.REOPENED task.deadline = None comment_props = { 'parent': task, 'title': DEF_UNCLAIMED_TITLE, 'content': DEF_UNCLAIMED, 'created_by': student_key.parent() } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def unclaimTaskTxn(): task.put() comment_txn() return db.run_in_transaction(unclaimTaskTxn)
def comments(self): """Returns the GCIComments that have the given task as parent. The results are sorted by the date on which they have been created. """ q = GCIComment.all() q.ancestor(self) q.order('created_on') return q.fetch(1000)
def task_delete_txn(task): """Performs all necessary operations in a single transaction when a task is deleted. """ to_delete = [] to_delete += GCIComment.all(keys_only=True).ancestor(task) to_delete += GCIWorkSubmission.all(keys_only=True).ancestor(task) to_delete += [task.key()] db.delete(to_delete)
def process_task(task): """Conversion to make GCI Tasks ID based and getting rid of unnecessary properties. """ if task.key().name(): # Get the values for all the properties in the GCITask model from the # old entity to create the new entity. new_task_properties = {} for prop in TASK_PROPERTIES: new_task_properties[prop] = getattr(task, prop) new_task_properties['org'] = task.scope new_task = GCITask(**new_task_properties) new_task_key = new_task.put() if new_task_key: # Update all the comments with the new task as the parent comments = GCIComment.all().ancestor(task).fetch(1000) for c in comments: new_comm_properties = {} for c_prop in COMMENT_PROPERTIES: new_comm_properties[c_prop] = getattr(c, c_prop) new_comment = GCIComment(parent=new_task_key, **new_comm_properties) # set these fields to behave like last_modified_on/by new_comment.modified_on = new_comment.created_on new_comment.modified_by = new_comment.created_by yield operation.db.Put(new_comment) yield operation.counters.Increment("comment_updated") # Update all the work submission entities with the new task as the parent work_submissions = GCIWorkSubmission.all().ancestor(task).fetch(1000) for ws in work_submissions: new_ws_properties = {} for ws_prop in WORK_SUBMISSION_PROPERTIES: new_ws_properties[ws_prop] = getattr(ws, ws_prop) new_ws = GCIWorkSubmission(parent=new_task_key, **new_ws_properties) yield operation.db.Put(new_ws) yield operation.counters.Increment("work_submission_updated") yield operation.counters.Increment("task_updated")
def closeTask(task, profile): """Closes the task. Args: task: task_model.GCITask entity. profile: GCIProfile of the user that closes the task. """ from soc.modules.gci.tasks.ranking_update import startUpdatingTask task.status = 'Closed' task.closed_on = datetime.datetime.now() task.deadline = None comment_props = { 'parent': task, 'title': DEF_CLOSED_TITLE, 'content': DEF_CLOSED, 'created_by': profile.key.parent().to_old_key() } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) student_key = ndb.Key.from_old_key( task_model.GCITask.student.get_value_for_datastore(task)) student = student_key.get() # student, who worked on the task, should receive a confirmation # having submitted his or her first task query = queryAllTasksClosedByStudent(student, keys_only=True) if query.get() is None: # this is the first task confirmation = profile_logic.sendFirstTaskConfirmationTxn(student, task) else: confirmation = lambda: None org_score_txn = org_score_logic.updateOrgScoreTxn(task, student) @db.transactional(xg=True) def closeTaskTxn(): task.put() comment_txn() startUpdatingTask(task, transactional=True) confirmation() org_score_txn() # TODO(daniel): move this to a transaction when other models are NDB student = student_key.get() student.student_data.number_of_completed_tasks += 1 student.put() return closeTaskTxn()
def turnaroundTime(task): from soc.modules.gci.logic import task as task_logic from soc.modules.gci.models.comment import GCIComment q = GCIComment.all() q.ancestor(task) q.filter('modified_by', None) q.filter('title', task_logic.DEF_ASSIGNED_TITLE) comments = sorted(q, key=lambda x: x.created_on) started = comments[-1] q = GCIComment.all() q.ancestor(task) q.filter('modified_by', None) q.filter('title', task_logic.DEF_SEND_FOR_REVIEW_TITLE) comments = sorted(q, key=lambda x: x.created_on) finished = comments[-1] q = GCIComment.all() q.ancestor(task) q.filter('modified_by', None) q.filter('title', task_logic.DEF_CLOSED_TITLE) approved = q.get() # there can only be one implementation = finished.created_on - started.created_on turnaround = approved.created_on - finished.created_on url = "http://www.google-melange.com/gci/task/view/google/gci2011/%d" return (url % task.key().id(), str(started.created_on), str(finished.created_on), str(approved.created_on), str(implementation), str(turnaround), task.difficulty_level, finished.created_by.name, approved.created_by.name, )
def transitFromClaimed(task): """Makes a state transition of a GCI Task from Claimed state to ActionNeeded. Args: task: The task_model.GCITask entity """ # deadline is extended by 24 hours. task.status = 'ActionNeeded' task.deadline = task.deadline + datetime.timedelta(hours=24) comment_props = { 'parent': task, 'title': DEF_ACTION_NEEDED_TITLE, 'content': DEF_ACTION_NEEDED, } comment = GCIComment(**comment_props) return task, comment
def transitFromActionNeeded(task): """Makes a state transition of a GCI Task from ActionNeeded state to Reopened state. Args: task: The task_model.GCITask entity """ # reopen the task task.student = None task.status = task_model.REOPENED task.deadline = None comment_props = { 'parent': task, 'title': DEF_REOPENED_TITLE, 'content': DEF_REOPENED, } comment = GCIComment(**comment_props) return task, comment
def transitFromNeedsWork(task): """Makes a state transition of a GCI Task from NeedsWork state to Reopened state. A task that has been marked as Needs(more)Work will NOT get a deadline extension and will be reopened immediately. Args: task: The task_model.GCITask entity """ task.student = None task.status = task_model.REOPENED task.deadline = None comment_props = { 'parent': task, 'title': DEF_REOPENED_TITLE, 'content': DEF_REOPENED, } comment = GCIComment(**comment_props) return task, comment
def transitFromNeedsReview(task): """Makes a state transition of a GCI Task that is in NeedsReview state. This state transition is special since it actually only clears the deadline field and does not change value of the state field. A Task is in this state when work has been submitted and it has not been reviewed before the original deadline runs out. Args: task: The task_model.GCITask entity """ # Clear the deadline since mentors are not forced to review work within a # certain period. task.deadline = None comment_props = { 'parent': task, 'title': DEF_NO_MORE_WORK_TITLE, 'content': DEF_NO_MORE_WORK, } comment = GCIComment(**comment_props) return task, comment
def needsWorkTask(task, profile): """Closes the task. Args: task: task_model.GCITask entity. profile: GCIProfile of the user that marks this task as needs more work. """ task.status = 'NeedsWork' comment_props = { 'parent': task, 'title': DEF_NEEDS_WORK_TITLE, 'content': DEF_NEEDS_WORK, 'created_by': profile.key.parent().to_old_key() } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def needsWorkTaskTxn(): task.put() comment_txn() return db.run_in_transaction(needsWorkTaskTxn)
def sendForReview(task, student): """Send in a task for review. Args: task: The task to send for review. student: Profile of the student that is sending in the work. """ task.status = 'NeedsReview' comment_props = { 'parent': task, 'title': DEF_SEND_FOR_REVIEW_TITLE, 'content': DEF_SEND_FOR_REVIEW, 'created_by': student.key.parent().to_old_key(), } comment = GCIComment(**comment_props) comment_txn = comment_logic.storeAndNotifyTxn(comment) def sendForReviewTxn(): task.put() comment_txn() return db.run_in_transaction(sendForReviewTxn)
def sendCommentNotificationMail(self, request, *args, **kwargs): """Appengine task that sends mail to the subscribed users. Expects the following to be present in the POST dict: comment_key: Specifies the comment id for which to send the notifications Args: request: Django Request object """ # TODO(ljvderijk): If all mails are equal we can sent one big bcc mail # set default batch size batch_size = 10 post_dict = request.POST comment_key = post_dict.get("comment_key") if not comment_key: # invalid task data, log and return OK return error_handler.logErrorAndReturnOK("Invalid createNotificationMail data: %s" % post_dict) comment_key = db.Key(comment_key) comment = GCIComment.get(comment_key) if not comment: # invalid comment specified, log and return OK return error_handler.logErrorAndReturnOK("Invalid comment specified: %s" % (comment_key)) task = comment.parent() subscription = GCITaskSubscription.all().ancestor(task).fetch(1) # check and retrieve the subscriber_start_key that has been done last idx = int(post_dict.get("subscriber_start_index", 0)) subscribers = db.get(subscription.subscribers[idx : idx + batch_size]) url_kwargs = { "sponsor": task.program.scope_path, "program": task.program.link_id, "id": task.key().id_or_name(), } task_url = "http://%(host)s%(task)s" % { "host": system.getHostname(), "task": reverse("gci_view_task", kwargs=url_kwargs), } # create the data for the mail to be sent message_properties = { "task_url": task_url, "redirect_url": "%(task_url)s#c%(cid)d" % {"task_url": task_url, "cid": comment.key().id_or_name()}, "comment_entity": comment, "task_entity": task, } subject = self.DEF_TASK_UPDATE_SUBJECT % {"program_name": task.program.short_name, "title": task.title} for subscriber in subscribers: # TODO(ljvderijk): enable sending of mail after template fixes # gci_notifications.sendTaskUpdateMail(subscriber, subject, # message_properties) pass if len(subscribers) == batch_size: # spawn task for sending out notifications to next set of subscribers next_start = idx + batch_size task_params = {"comment_key": str(comment_key), "subscriber_start_index": next_start} task_url = "/tasks/gci/task/mail/comment" new_task = taskqueue.Task(params=task_params, url=task_url) new_task.add("mail") # return OK return http.HttpResponse()
from soc.modules.gci.models.comment import GCIComment from soc.modules.gci.models.mentor import GCIMentor from soc.modules.gci.models.org_admin import GCIOrgAdmin from soc.modules.gci.models.profile import GCIProfile from soc.modules.gci.models.student import GCIStudent from soc.modules.gci.models.task import GCITask from soc.modules.gci.models.task_subscription import GCITaskSubscription from soc.modules.gci.models.work_submission import GCIWorkSubmission TASK_PROPERTIES = GCITask.properties() # We do not want history property in the new entities in any more because # we are not using it TASK_PROPERTIES.pop('scope', 'history') COMMENT_PROPERTIES = GCIComment.properties() WORK_SUBMISSION_PROPERTIES = GCIWorkSubmission.properties() def process_task(task): """Conversion to make GCI Tasks ID based and getting rid of unnecessary properties. """ if task.key().name(): # Get the values for all the properties in the GCITask model from the # old entity to create the new entity. new_task_properties = {} for prop in TASK_PROPERTIES: new_task_properties[prop] = getattr(task, prop) new_task_properties['org'] = task.scope