def test_assign_task(self):
        entry_task = TaskFactory(project=self.projects['base_test_project'],
                                 status=Task.Status.AWAITING_PROCESSING,
                                 step=self.test_step)

        # Assign entry-level task to entry-level worker
        entry_task = assign_task(self.workers[0].id, entry_task.id)
        self.assertTrue(is_worker_assigned_to_task(self.workers[0],
                                                   entry_task))
        self.assertEquals(entry_task.status, Task.Status.PROCESSING)
        self.assertEquals(entry_task.assignments.count(), 1)

        # Attempt to assign task which isn't awaiting a new assignment
        invalid = (Task.Status.PROCESSING, Task.Status.ABORTED,
                   Task.Status.REVIEWING, Task.Status.COMPLETE,
                   Task.Status.POST_REVIEW_PROCESSING)
        for status in invalid:
            invalid_status_task = Task.objects.create(
                project=self.projects['base_test_project'],
                status=status,
                step=self.test_step)

            with self.assertRaises(TaskAssignmentError):
                invalid_status_task = assign_task(self.workers[0].id,
                                                  invalid_status_task.id)

        # Attempt to assign review task to worker already in review hierarchy
        review_task = Task.objects.create(
            project=self.projects['base_test_project'],
            status=Task.Status.PENDING_REVIEW,
            step=self.test_step)
        test_data = {'test_assign': True}
        TaskAssignmentFactory(worker=self.workers[1],
                              task=review_task,
                              status=TaskAssignment.Status.SUBMITTED,
                              in_progress_task_data=test_data,
                              snapshots=empty_snapshots())

        with self.assertRaises(TaskAssignmentError):
            assign_task(self.workers[1].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Attempt to assign review task to worker not certified for task
        with self.assertRaises(WorkerCertificationError):
            assign_task(self.workers[2].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Assign review task to review worker
        self.assertEquals(review_task.assignments.count(), 1)
        review_task = assign_task(self.workers[3].id, review_task.id)
        self.assertEquals(review_task.assignments.count(), 2)
        self.assertEqual(
            current_assignment(review_task).worker, self.workers[3])
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)
        self.assertEquals(review_task.status, Task.Status.REVIEWING)
    def test_task_assignment_saving(self):
        """
        Ensure that workers are required for human tasks,
        and no workers are required for machine tasks.
        """
        workflow_version = self.workflow_versions['test_workflow_2']
        simple_machine = self.workflow_steps[
            workflow_version.slug]['simple_machine']
        project = Project.objects.create(workflow_version=workflow_version,
                                         short_description='',
                                         priority=0,
                                         task_class=0)
        task = Task.objects.create(project=project,
                                   status=Task.Status.PROCESSING,
                                   step=simple_machine)

        # We expect an error because a worker
        # is being saved on a machine task.
        with self.assertRaises(ModelSaveError):
            TaskAssignment.objects.create(worker=self.workers[0],
                                          task=task,
                                          status=0,
                                          in_progress_task_data={},
                                          snapshots=empty_snapshots())

        human_step = self.workflow_steps[workflow_version.slug]['step4']
        task = Task.objects.create(project=project,
                                   status=Task.Status.PROCESSING,
                                   step=human_step)

        # We expect an error because no worker
        # is being saved on a human task
        with self.assertRaises(ModelSaveError):
            TaskAssignment.objects.create(task=task,
                                          status=0,
                                          in_progress_task_data={},
                                          snapshots=empty_snapshots())
    def test_task_assignment_saving(self):
        """
        Ensure that workers are required for human tasks,
        and no workers are required for machine tasks.
        """
        workflow_version = self.workflow_versions['test_workflow_2']
        simple_machine = self.workflow_steps[
            workflow_version.slug]['simple_machine']
        project = Project.objects.create(workflow_version=workflow_version,
                                         short_description='',
                                         priority=0,
                                         task_class=0)
        task = Task.objects.create(project=project,
                                   status=Task.Status.PROCESSING,
                                   step=simple_machine)

        # We expect an error because a worker
        # is being saved on a machine task.
        with self.assertRaises(ModelSaveError):
            TaskAssignment.objects.create(worker=self.workers[0],
                                          task=task,
                                          status=0,
                                          in_progress_task_data={},
                                          snapshots=empty_snapshots())

        human_step = self.workflow_steps[workflow_version.slug]['step4']
        task = Task.objects.create(project=project,
                                   status=Task.Status.PROCESSING,
                                   step=human_step)

        # We expect an error because no worker
        # is being saved on a human task
        with self.assertRaises(ModelSaveError):
            TaskAssignment.objects.create(task=task,
                                          status=0,
                                          in_progress_task_data={},
                                          snapshots=empty_snapshots())
Example #4
0
    def test_project_information(self):
        project = self.projects['base_test_project']
        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'project_id': project.id},
            format='json')
        self.assertEquals(response.status_code, 200)
        returned = json.loads(response.content.decode('utf-8'))

        unimportant_keys = (
            'id',
            'task',
            'short_description',
            'start_datetime',
        )

        def delete_keys(obj):
            if isinstance(obj, list):
                for item in obj:
                    delete_keys(item)

            elif isinstance(obj, dict):
                for key in unimportant_keys:
                    try:
                        del obj[key]
                    except KeyError:
                        pass
                for value in obj.values():
                    delete_keys(value)

        delete_keys(returned)
        del returned['tasks']['step1']['project']

        expected = {
            'project': {
                'task_class': 1,
                'workflow_slug': 'test_workflow',
                'project_data': {},
                'review_document_url': None,
                'priority': 0,
            },
            'tasks': {
                'step1': {
                    'assignments': [{
                        'snapshots': empty_snapshots(),
                        'status': 'Submitted',
                        'in_progress_task_data': {'test_key': 'test_value'},
                        'worker': 'test_user_0',
                    }],
                    'latest_data': {
                        'test_key': 'test_value'
                    },
                    'status': 'Pending Review',
                    'step_slug': 'step1',
                }
            },
            'steps': [
                ['step1', 'The longer description of the first step'],
                ['step2', 'The longer description of the second step'],
                ['step3', 'The longer description of the third step']
            ]
        }

        self.assertEquals(returned, expected)

        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'project_id': -1},
            format='json')
        self.ensure_response(response,
                             {'error': 400,
                              'message': 'No project for given id'},
                             400)

        # Getting project info without a project_id should fail.
        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'projetc_id': project.id},  # Typo.
            format='json')
        self.ensure_response(response,
                             {'error': 400,
                              'message': 'project_id is required'},
                             400)

        # Retrieve the third project, which has no task assignments.
        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'project_id': self.projects['no_task_assignments'].id},
            format='json')
        returned = json.loads(response.content.decode('utf-8'))
        del returned['tasks']['step1']['id']
        del returned['tasks']['step1']['project']
        self.assertEquals(response.status_code, 200)
        self.assertEquals(returned['tasks'], {
            'step1': {
                'assignments': [],
                'latest_data': None,
                'status': 'Awaiting Processing',
                'step_slug': 'step1'
            }
        })
Example #5
0
def assign_task(worker_id, task_id):
    """
    Return a given task after assigning or reassigning it to the specified
    worker.

    Args:
        worker_id (int):
            The ID of the worker to be assigned.
        task_id (int):
            The ID of the task to be assigned.

    Returns:
        task (orchestra.models.Task):
            The newly assigned task.

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            The specified worker is already assigned to the given task
            or the task status is not compatible with new assignment.
        orchestra.core.errors.WorkerCertificationError:
            The specified worker is not certified for the given task.
    """
    worker = Worker.objects.get(id=worker_id)
    task = Task.objects.get(id=task_id)
    required_role, requires_reassign = _role_required_to_assign(task)
    assignment = current_assignment(task)
    if not _worker_certified_for_task(worker, task, required_role):
        raise WorkerCertificationError('Worker not certified for this task.')
    if is_worker_assigned_to_task(worker, task):
        raise TaskAssignmentError('Worker already assigned to this task.')

    # If task is currently in progress, reassign it
    if requires_reassign:
        assignment.worker = worker
        assignment.save()
        add_worker_to_project_team(worker, task.project)
        return task

    # Otherwise, create new assignment
    assignment_counter = task.assignments.count()
    in_progress_task_data = {}

    if required_role == WorkerCertification.Role.REVIEWER:
        # In-progress task data is the latest
        # submission by a previous worker
        in_progress_task_data = assignment.in_progress_task_data

    previous_status = task.status
    if previous_status == Task.Status.AWAITING_PROCESSING:
        task.status = Task.Status.PROCESSING
    elif previous_status == Task.Status.PENDING_REVIEW:
        task.status = Task.Status.REVIEWING
    else:
        raise TaskAssignmentError('Status incompatible with new assignment')
    task.save()

    (TaskAssignment.objects
        .create(worker=worker,
                task=task,
                status=TaskAssignment.Status.PROCESSING,
                assignment_counter=assignment_counter,
                in_progress_task_data=in_progress_task_data,
                snapshots=empty_snapshots()))

    add_worker_to_project_team(worker, task.project)
    notify_status_change(task, previous_status)
    return task
Example #6
0
    def test_project_information(self):
        project = self.projects['base_test_project']
        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'project_id': project.id},
            format='json')
        self.assertEquals(response.status_code, 200)
        returned = json.loads(response.content.decode('utf-8'))

        unimportant_keys = (
            'id',
            'task',
            'short_description',
            'start_datetime',
        )

        def delete_keys(obj):
            if isinstance(obj, list):
                for item in obj:
                    delete_keys(item)

            elif isinstance(obj, dict):
                for key in unimportant_keys:
                    try:
                        del obj[key]
                    except KeyError:
                        pass
                for value in obj.values():
                    delete_keys(value)

        delete_keys(returned)
        del returned['tasks']['step1']['project']

        expected = {
            'project': {
                'task_class': 1,
                'workflow_slug': 'w1',
                'workflow_version_slug': 'test_workflow',
                'project_data': {},
                'team_messages_url': None,
                'priority': 0,
            },
            'tasks': {
                'step1': {
                    'assignments': [{
                        'snapshots': empty_snapshots(),
                        'status': 'Submitted',
                        'in_progress_task_data': {
                            'test_key': 'test_value'
                        },
                        'worker': {
                            'username': self.workers[0].user.username,
                            'first_name': self.workers[0].user.first_name,
                            'last_name': self.workers[0].user.last_name,
                        },
                    }],
                    'latest_data': {
                        'test_key': 'test_value'
                    },
                    'status':
                    'Pending Review',
                    'step_slug':
                    'step1',
                }
            },
            'steps': [{
                'slug': 'step1',
                'description': 'The longer description of the first step',
                'is_human': True
            }, {
                'slug': 'step2',
                'description': 'The longer description of the second step',
                'is_human': True
            }, {
                'slug': 'step3',
                'description': 'The longer description of the third step',
                'is_human': True
            }]
        }

        self.assertEquals(returned, expected)

        response = self.api_client.post(
            '/orchestra/api/project/project_information/', {'project_id': -1},
            format='json')
        self.ensure_response(response, {
            'error': 400,
            'message': 'No project for given id'
        }, 400)

        # Getting project info without a project_id should fail.
        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'projetc_id': project.id},  # Typo.
            format='json')
        self.ensure_response(response, {
            'error': 400,
            'message': 'project_id is required'
        }, 400)

        # Retrieve the third project, which has no task assignments.
        response = self.api_client.post(
            '/orchestra/api/project/project_information/',
            {'project_id': self.projects['no_task_assignments'].id},
            format='json')
        returned = json.loads(response.content.decode('utf-8'))
        for key in ('id', 'project', 'start_datetime'):
            del returned['tasks']['step1'][key]
        self.assertEquals(response.status_code, 200)
        self.assertEquals(
            returned['tasks'], {
                'step1': {
                    'assignments': [],
                    'latest_data': None,
                    'status': 'Awaiting Processing',
                    'step_slug': 'step1'
                }
            })
Example #7
0
    def test_legal_get_next_task_status(self):
        task = self.tasks['processing_task']
        workflow = get_workflow_by_slug(task.project.workflow_slug)
        step = workflow.get_step(task.step_slug)
        step.review_policy = {}

        task.status = Task.Status.PROCESSING
        with self.assertRaises(ReviewPolicyError):
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.SUBMIT)

        step.review_policy = {'policy': 'sampled_review',
                              'rate': 1,
                              'max_reviews': 1}
        self.assertEquals(
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.SUBMIT),
            Task.Status.PENDING_REVIEW)

        step.review_policy = {'policy': 'sampled_review',
                              'rate': 0,
                              'max_reviews': 1}
        self.assertEquals(
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.SUBMIT),
            Task.Status.COMPLETE)

        task.status = Task.Status.POST_REVIEW_PROCESSING
        self.assertEquals(
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.SUBMIT),
            Task.Status.REVIEWING)

        task = self.tasks['review_task']
        task.status = Task.Status.REVIEWING

        step.review_policy = {'policy': 'sampled_review',
                              'rate': 1,
                              'max_reviews': 0}
        self.assertEquals(
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.ACCEPT),
            Task.Status.COMPLETE)

        step.review_policy = {'policy': 'sampled_review',
                              'rate': 1,
                              'max_reviews': 2}
        self.assertEquals(
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.ACCEPT),
            Task.Status.PENDING_REVIEW)

        # after max reviews done a task goes to state complete
        TaskAssignment.objects.create(worker=self.workers[1],
                                      task=task,
                                      status=TaskAssignment.Status.SUBMITTED,
                                      assignment_counter=1,
                                      in_progress_task_data={},
                                      snapshots=empty_snapshots())
        task.save()
        step.review_policy = {'policy': 'sampled_review',
                              'rate': 1,
                              'max_reviews': 1}
        self.assertEquals(
            get_next_task_status(task,
                                 TaskAssignment.SnapshotType.ACCEPT),
            Task.Status.COMPLETE)
Example #8
0
def assign_task(worker_id, task_id):
    """
    Return a given task after assigning or reassigning it to the specified
    worker.

    Args:
        worker_id (int):
            The ID of the worker to be assigned.
        task_id (int):
            The ID of the task to be assigned.

    Returns:
        task (orchestra.models.Task):
            The newly assigned task.

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            The specified worker is already assigned to the given task
            or the task status is not compatible with new assignment.
        orchestra.core.errors.WorkerCertificationError:
            The specified worker is not certified for the given task.
    """
    worker = Worker.objects.get(id=worker_id)
    task = Task.objects.get(id=task_id)

    roles = {
        Task.Status.AWAITING_PROCESSING: WorkerCertification.Role.ENTRY_LEVEL,
        Task.Status.PENDING_REVIEW: WorkerCertification.Role.REVIEWER
    }

    required_role = roles.get(task.status)
    if required_role is None:
        raise TaskAssignmentError('Status incompatible with new assignment')

    assignment = current_assignment(task)
    if not _worker_certified_for_task(worker, task, required_role):
        raise WorkerCertificationError('Worker not certified for this task.')
    if is_worker_assigned_to_task(worker, task):
        raise TaskAssignmentError('Worker already assigned to this task.')

    assignment_counter = task.assignments.count()
    in_progress_task_data = {}

    if assignment:
        # In-progress task data is the latest
        # submission by a previous worker
        in_progress_task_data = assignment.in_progress_task_data

    previous_status = task.status
    if previous_status == Task.Status.AWAITING_PROCESSING:
        task.status = Task.Status.PROCESSING
    elif previous_status == Task.Status.PENDING_REVIEW:
        task.status = Task.Status.REVIEWING
    task.save()

    (TaskAssignment.objects.create(worker=worker,
                                   task=task,
                                   status=TaskAssignment.Status.PROCESSING,
                                   assignment_counter=assignment_counter,
                                   in_progress_task_data=in_progress_task_data,
                                   snapshots=empty_snapshots()))

    add_worker_to_project_team(worker, task.project)
    notify_status_change(task, previous_status)
    return task
    def test_project_information_api(self):
        project = self.projects['project_management_project']
        response = self.api_client.post(
            reverse(
                'orchestra:orchestra:project_management:'
                'project_information'),
            json.dumps({
                'project_id': project.id,
            }),
            content_type='application/json')
        self.assertEquals(response.status_code, 200)
        returned = json.loads(response.content.decode('utf-8'))

        # Skipping the `tasks` key/value pair for this test
        expected_project = {
            'task_class': project.task_class,
            'start_datetime': '2015-10-12T00:00:00Z',
            'team_messages_url': None,
            'admin_url': settings.ORCHESTRA_URL + reverse(
                'admin:orchestra_project_change',
                args=(project.id,)),
            'priority': project.priority,
            'project_data': project.project_data,
            'short_description': project.short_description,
            'id': project.id,
            'workflow_slug': project.workflow_version.workflow.slug
        }
        self.assertEquals(
            expected_project,
            {k: returned['project'][k] for k in expected_project.keys()})

        sample_task = returned['tasks']['step1']
        task = project.tasks.get(step__slug='step1')
        expected_task = {
            'status': 'Post-review Processing',
            'start_datetime': '2015-10-12T01:00:00Z',
            'admin_url': settings.ORCHESTRA_URL + reverse(
                'admin:orchestra_task_change',
                args=(task.id,)),
            'latest_data': {
                'test_key': 'test_value'
            },
            'project': task.project.id,
            'step_slug': 'step1',
            'id': task.id
        }

        self.assertEquals(
            expected_task,
            {k: sample_task[k] for k in expected_task.keys()})

        sample_assignment = sample_task['assignments'][0]
        assignment = assignment_history(task)[0]
        expected_assignment = {
            'status': 'Submitted',
            'start_datetime': '2015-10-12T02:00:00Z',
            'task': assignment.task.id,
            'admin_url': settings.ORCHESTRA_URL + reverse(
                'admin:orchestra_taskassignment_change',
                args=(assignment.id,)),
            'worker': {
                'username': assignment.worker.user.username,
                'first_name': assignment.worker.user.first_name,
                'last_name': assignment.worker.user.last_name,
                'id': assignment.worker.id,
            },
            'snapshots': empty_snapshots(),
            'iterations': [
                {
                    'start_datetime': '2015-10-12T01:00:00',
                    'end_datetime': '2015-10-12T02:00:00',
                }
            ],
            'in_progress_task_data': {},
            'id': assignment.id
        }

        self.assertEquals(
            expected_assignment,
            {k: sample_assignment[k] for k in expected_assignment.keys()})
Example #10
0
    def test_legal_get_next_task_status(self):
        task = self.tasks['awaiting_processing']
        step = task.step

        task.status = Task.Status.PROCESSING
        step.review_policy = {}
        step.save()
        with self.assertRaises(ReviewPolicyError):
            get_next_task_status(task, TaskAssignment.SnapshotType.SUBMIT)

        step.review_policy = {
            'policy': 'sampled_review',
            'rate': 1,
            'max_reviews': 1
        }
        step.save()
        self.assertEquals(
            get_next_task_status(task, TaskAssignment.SnapshotType.SUBMIT),
            Task.Status.PENDING_REVIEW)

        step.review_policy = {
            'policy': 'sampled_review',
            'rate': 0,
            'max_reviews': 1
        }
        step.save()
        self.assertEquals(
            get_next_task_status(task, TaskAssignment.SnapshotType.SUBMIT),
            Task.Status.COMPLETE)

        task.status = Task.Status.POST_REVIEW_PROCESSING
        self.assertEquals(
            get_next_task_status(task, TaskAssignment.SnapshotType.SUBMIT),
            Task.Status.REVIEWING)

        task = self.tasks['review_task']
        task.status = Task.Status.REVIEWING

        step.review_policy = {
            'policy': 'sampled_review',
            'rate': 1,
            'max_reviews': 0
        }
        step.save()
        self.assertEquals(
            get_next_task_status(task, TaskAssignment.SnapshotType.ACCEPT),
            Task.Status.COMPLETE)

        step.review_policy = {
            'policy': 'sampled_review',
            'rate': 1,
            'max_reviews': 2
        }
        step.save()
        self.assertEquals(
            get_next_task_status(task, TaskAssignment.SnapshotType.ACCEPT),
            Task.Status.PENDING_REVIEW)

        # after max reviews done a task goes to state complete
        TaskAssignment.objects.create(worker=self.workers[1],
                                      task=task,
                                      status=TaskAssignment.Status.SUBMITTED,
                                      assignment_counter=1,
                                      in_progress_task_data={},
                                      snapshots=empty_snapshots())
        task.save()
        step.review_policy = {
            'policy': 'sampled_review',
            'rate': 1,
            'max_reviews': 1
        }
        step.save()
        self.assertEquals(
            get_next_task_status(task, TaskAssignment.SnapshotType.ACCEPT),
            Task.Status.COMPLETE)
Example #11
0
    def test_assign_task(self):
        entry_task = TaskFactory(
            project=self.projects['base_test_project'],
            status=Task.Status.AWAITING_PROCESSING,
            step=self.test_step)

        # Assign entry-level task to entry-level worker
        entry_task = assign_task(self.workers[0].id, entry_task.id)
        self.assertTrue(is_worker_assigned_to_task(self.workers[0],
                                                   entry_task))
        self.assertEquals(entry_task.status, Task.Status.PROCESSING)
        self.assertEquals(entry_task.assignments.count(), 1)

        # Attempt to assign task which isn't awaiting a new assignment
        invalid = (Task.Status.PROCESSING, Task.Status.ABORTED,
                   Task.Status.REVIEWING, Task.Status.COMPLETE,
                   Task.Status.POST_REVIEW_PROCESSING)
        for status in invalid:
            invalid_status_task = Task.objects.create(
                project=self.projects['base_test_project'],
                status=status,
                step=self.test_step)

            with self.assertRaises(TaskAssignmentError):
                invalid_status_task = assign_task(
                    self.workers[0].id, invalid_status_task.id)

        # Attempt to assign review task to worker already in review hierarchy
        review_task = Task.objects.create(
            project=self.projects['base_test_project'],
            status=Task.Status.PENDING_REVIEW,
            step=self.test_step)
        test_data = {'test_assign': True}
        TaskAssignmentFactory(
            worker=self.workers[1],
            task=review_task,
            status=TaskAssignment.Status.SUBMITTED,
            in_progress_task_data=test_data,
            snapshots=empty_snapshots())

        with self.assertRaises(TaskAssignmentError):
            assign_task(self.workers[1].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Attempt to assign review task to worker not certified for task
        with self.assertRaises(WorkerCertificationError):
            assign_task(self.workers[2].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Assign review task to review worker
        self.assertEquals(review_task.assignments.count(), 1)
        review_task = assign_task(self.workers[3].id, review_task.id)
        self.assertEquals(review_task.assignments.count(), 2)
        self.assertEqual(
            current_assignment(review_task).worker, self.workers[3])
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)
        self.assertEquals(
            review_task.status, Task.Status.REVIEWING)
Example #12
0
def project_management_information(project_id):
    project = Project.objects.get(id=project_id)
    df = work_time_df([project], human_only=False, complete_tasks_only=False)
    project_information = get_project_information(project.id)
    project_information['project']['status'] = dict(
        Project.STATUS_CHOICES).get(project.status, None)
    project_information['project']['admin_url'] = urljoin(
        settings.ORCHESTRA_URL,
        urlresolvers.reverse('admin:orchestra_project_change',
                             args=(project_id, )))

    for slug, task in project_information['tasks'].items():
        task['admin_url'] = urljoin(
            settings.ORCHESTRA_URL,
            urlresolvers.reverse('admin:orchestra_task_change',
                                 args=(task['id'], )))

        for assignment in task['assignments']:
            assignment['admin_url'] = urljoin(
                settings.ORCHESTRA_URL,
                urlresolvers.reverse('admin:orchestra_taskassignment_change',
                                     args=(assignment['id'], )))

            iterations = df[(df.worker == assignment['worker']['username'])
                            & (df.task_id == task['id'])]
            iterations = iterations[['start_datetime', 'end_datetime']]
            assignment['iterations'] = []
            for idx, info in iterations.T.items():
                iteration = info.to_dict()
                assignment['iterations'].append(iteration)
            if assignment['status'] == 'Processing':
                last_iteration_end = assignment['start_datetime']
                last_assignment = last_snapshotted_assignment(task['id'])
                if last_assignment and len(assignment['iterations']) > 1:
                    last_iteration_end = (
                        last_assignment.snapshots['snapshots'][-1]['datetime'])

                assignment['iterations'].append({
                    'start_datetime': last_iteration_end,
                    'end_datetime': timezone.now()
                })

        if task['status'] in ('Awaiting Processing', 'Pending Review'):
            last_assignment_end = task['start_datetime']
            last_assignment = last_snapshotted_assignment(task['id'])
            if last_assignment:
                last_assignment_end = (
                    last_assignment.snapshots['snapshots'][-1]['datetime'])
            task['assignments'].append({
                'iterations': [{
                    'start_datetime': last_assignment_end,
                    'end_datetime': timezone.now()
                }],
                'snapshots':
                empty_snapshots(),
                'start_datetime':
                last_assignment_end,
                'status':
                'Processing',
                'task':
                task['id'],
                'worker': {
                    'id': None,
                    'username': None
                },
            })
    return project_information