def test_batch_to_csv_variable_number_of_answers(self): project = Project(name='test', html_template='<p>${letter}</p><textarea>') project.save() batch = Batch(project=project) batch.save() task1 = Task( batch=batch, completed=True, input_csv_fields={'letter': 'a'}, ) task1.save() TaskAssignment( answers={ '1': 1, '2': 2 }, assigned_to=None, completed=True, task=task1, ).save() task2 = Task( batch=batch, completed=True, input_csv_fields={'letter': 'b'}, ) task2.save() TaskAssignment(answers={ '3': 3, '4': 4 }, assigned_to=None, completed=True, task=task2).save() task3 = Task( batch=batch, completed=True, input_csv_fields={'letter': 'c'}, ) task3.save() TaskAssignment(answers={ '3': 3, '2': 2 }, assigned_to=None, completed=True, task=task3).save() csv_output = StringIO() batch.to_csv(csv_output) rows = csv_output.getvalue().split() self.assertTrue( '"Input.letter","Answer.1","Answer.2","Answer.3","Answer.4"' in rows[0]) self.assertTrue(any(['"a","1","2","",""' in row for row in rows[1:]])) self.assertTrue(any(['"b","","","3","4"' in row for row in rows[1:]])) self.assertTrue(any(['"c","","2","3",""' in row for row in rows[1:]]))
def test_task_marked_as_completed_two_way_redundancy(self): # When assignment_per_task==2, completing 2 Assignments marks Task as complete project = Project( name='test', html_template='<p>${number} - ${letter}</p><textarea>') project.save() batch = Batch(name='test', project=project) batch.assignments_per_task = 2 batch.save() task = Task(batch=batch, input_csv_fields={ 'number': '1', 'letter': 'a' }) task.save() self.assertFalse(task.completed) self.assertFalse(batch.completed) TaskAssignment(assigned_to=None, completed=True, task=task).save() task.refresh_from_db() batch.refresh_from_db() self.assertFalse(task.completed) self.assertFalse(batch.completed) TaskAssignment(assigned_to=None, completed=True, task=task).save() task.refresh_from_db() batch.refresh_from_db() self.assertTrue(task.completed) self.assertTrue(batch.completed)
def setUp(self): """ Sets up Project, Task objects, and saves them to the DB. The Project form HTML only displays the one input variable. The Task has inputs and answers and refers to the Project form. """ admin = User.objects.create_superuser('admin', '*****@*****.**', 'secret') project = Project(name='test', html_template="<p>${foo}</p><textarea>") project.created_by = admin project.updated_by = admin save_model(project) batch = Batch(name='test', project=project, filename='test.csv') batch.created_by = admin batch.updated_by = admin save_model(batch) task = Task( batch=batch, input_csv_fields={'foo': 'bar'}, completed=True, ) task.save() self.task = task self.task_assignment = TaskAssignment( answers={ "comment": "\u221e", "userDisplayLanguage": "", "sentence_textbox_3_verb1": "", "city": "", "sentence_textbox_1_verb6": "", "sentence_textbox_1_verb7": "", "sentence_textbox_1_verb4": "", "sentence_textbox_1_verb5": "", "sentence_textbox_1_verb2": "", "sentence_textbox_1_verb3": "", "sentence_textbox_1_verb1": "", "sentence_textbox_2_verb4": "", "csrfmiddlewaretoken": "7zxQ9Yyug6Nsnm4nLky9p8ObJwNipdu8", "sentence_drop_2_verb3": "foo", "sentence_drop_2_verb2": "foo", "sentence_drop_2_verb1": "foo", "sentence_textbox_2_verb1": "", "sentence_textbox_2_verb3": "", "sentence_drop_2_verb4": "foo", "sentence_textbox_2_verb2": "", "submitit": "Submit", "browserInfo": "", "sentence_drop_1_verb1": "foo", "sentence_drop_1_verb2": "foo", "sentence_drop_1_verb3": "foo", "sentence_drop_1_verb4": "foo", "sentence_drop_1_verb5": "foo", "sentence_drop_1_verb6": "foo", "sentence_drop_1_verb7": "foo", "country": "", "sentence_drop_3_verb1": "foo", "ipAddress": "", "region": "" }, assigned_to=None, completed=True, task=task ) self.task_assignment.save()
class TestTaskAssignmentIFrame(TestCase): def setUp(self): self.user = User.objects.create_user('testuser', password='******') self.admin = User.objects.create_superuser('admin', '*****@*****.**', 'secret') self.project = Project(name="Test", login_required=False) self.project.created_by = self.admin self.project.updated_by = self.admin self.project.save() batch = Batch(project=self.project) batch.save() self.task = Task(input_csv_fields={}, batch=batch) self.task.save() self.task_assignment = TaskAssignment(task=self.task) self.task_assignment.save() def test_get_task_assignment_iframe_with_wrong_user(self): User.objects.create_user('wrong_user', password='******') self.task_assignment.assigned_to = self.user self.task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('task_assignment_iframe', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertTrue('You do not have permission to work on the Task Assignment with ID' in str(messages[0])) def test_template_with_submit_button(self): self.project.html_template = \ '<input id="my_submit_button" type="submit" value="MySubmit" />' save_model(self.project) self.project.refresh_from_db() self.assertTrue(self.project.html_template_has_submit_button) client = django.test.Client() response = client.get(reverse('task_assignment_iframe', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 200) self.assertTrue(b'my_submit_button' in response.content) self.assertFalse(b'submitButton' in response.content) def test_template_without_submit_button(self): self.project.form = '<input id="foo" type="text" value="MyText" />' self.project.save() self.project.refresh_from_db() self.assertFalse(self.project.html_template_has_submit_button) client = django.test.Client() response = client.get(reverse('task_assignment_iframe', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 200) self.assertFalse(b'my_submit_button' in response.content) self.assertTrue(b'submitButton' in response.content)
def test_available_tasks_for__apt_is_1(self): batch = Batch(assignments_per_task=1, project=self.project) batch.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) self.assertEqual( Batch.available_task_counts_for(self.batch_query, self.user)[batch.id], 0) self.assertEqual(batch.next_available_task_for(self.user), None) task = Task(batch=batch, ) task.save() self.assertEqual(batch.total_available_tasks_for(self.user), 1) self.assertEqual( Batch.available_task_counts_for(self.batch_query, self.user)[batch.id], 1) self.assertEqual(batch.next_available_task_for(self.user), task) task_assignment = TaskAssignment( assigned_to=self.user, completed=False, task=task, ) task_assignment.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) self.assertEqual( Batch.available_task_counts_for(self.batch_query, self.user)[batch.id], 0) self.assertEqual(batch.next_available_task_for(self.user), None)
def setUp(self): """ Sets up Project, Task objects, and saves them to the DB. The Project form HTML only displays the one input variable. The Task has inputs and answers and refers to the Project form. """ project = Project(name='test', html_template="<p>${foo}</p>") project.save() batch = Batch(project=project) batch.save() task = Task( batch=batch, input_csv_fields={u'foo': u'bar'}, completed=True, ) task.save() self.task = task self.task_assignment = TaskAssignment(answers={ u"comment": u"\u221e", u"userDisplayLanguage": u"", u"sentence_textbox_3_verb1": u"", u"city": u"", u"sentence_textbox_1_verb6": u"", u"sentence_textbox_1_verb7": u"", u"sentence_textbox_1_verb4": u"", u"sentence_textbox_1_verb5": u"", u"sentence_textbox_1_verb2": u"", u"sentence_textbox_1_verb3": u"", u"sentence_textbox_1_verb1": u"", u"sentence_textbox_2_verb4": u"", u"csrfmiddlewaretoken": u"7zxQ9Yyug6Nsnm4nLky9p8ObJwNipdu8", u"sentence_drop_2_verb3": u"foo", u"sentence_drop_2_verb2": u"foo", u"sentence_drop_2_verb1": u"foo", u"sentence_textbox_2_verb1": u"", u"sentence_textbox_2_verb3": u"", u"sentence_drop_2_verb4": u"foo", u"sentence_textbox_2_verb2": u"", u"submitit": u"Submit", u"browserInfo": u"", u"sentence_drop_1_verb1": u"foo", u"sentence_drop_1_verb2": u"foo", u"sentence_drop_1_verb3": u"foo", u"sentence_drop_1_verb4": u"foo", u"sentence_drop_1_verb5": u"foo", u"sentence_drop_1_verb6": u"foo", u"sentence_drop_1_verb7": u"foo", u"country": u"", u"sentence_drop_3_verb1": u"foo", u"ipAddress": u"", u"region": u"" }, assigned_to=None, completed=True, task=task) self.task_assignment.save()
def setUp(self): self.project = Project(login_required=False) self.project.save() batch = Batch(project=self.project) batch.save() self.task = Task(input_csv_fields={}, batch=batch) self.task.save() self.task_assignment = TaskAssignment(task=self.task) self.task_assignment.save()
def test_return_task_assignment_as_anonymous_user(self): task_assignment = TaskAssignment( assigned_to=None, task=self.task ) task_assignment.save() client = django.test.Client() response = client.get(reverse('return_task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index'))
def setUp(self): project = Project(login_required=False, name='foo', html_template='<p></p><textarea>') project.save() batch = Batch(project=project) batch.save() self.task = Task(batch=batch, input_csv_fields='{}') self.task.save() self.task_assignment = TaskAssignment(assigned_to=None, completed=False, task=self.task) self.task_assignment.save()
def test_return_task_assignment_as_anonymous_user(self): task_assignment = TaskAssignment(assigned_to=None, task=self.task) task_assignment.save() client = django.test.Client() response = client.get( reverse('return_task_assignment', kwargs={ 'task_id': self.task.id, 'task_assignment_id': task_assignment.id })) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index'))
def setUp(self): self.user = User.objects.create_user('testuser', password='******') self.admin = User.objects.create_superuser('admin', '*****@*****.**', 'secret') self.project = Project(name="Test", login_required=False) self.project.created_by = self.admin self.project.updated_by = self.admin self.project.save() batch = Batch(project=self.project) batch.save() self.task = Task(input_csv_fields={}, batch=batch) self.task.save() self.task_assignment = TaskAssignment(task=self.task) self.task_assignment.save()
def test_available_tasks_for__aph_is_2(self): batch = Batch(assignments_per_task=2, project=self.project) batch.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) task = Task(batch=batch, ) task.save() self.assertEqual(batch.total_available_tasks_for(self.user), 1) task_assignment = TaskAssignment( assigned_to=self.user, completed=False, task=task, ) task_assignment.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) other_user = User.objects.create_user('other_user', password='******') self.assertEqual(batch.total_available_tasks_for(other_user), 1) task_assignment = TaskAssignment( assigned_to=other_user, completed=False, task=task, ) task_assignment.save() self.assertEqual(batch.total_available_tasks_for(other_user), 0)
def test_batch_to_csv(self): template = '<p>${number} - ${letter}</p><textarea>' project = Project.objects.create(name='test', html_template=template) batch = Batch.objects.create(project=project) user = User.objects.create(username='******') task1 = Task( batch=batch, completed=True, input_csv_fields={ 'number': '1', 'letter': 'a' }, ) task1.save() ta = TaskAssignment( answers={'combined': '1a'}, assigned_to=user, completed=True, task=task1, ) ta.save() time_string = ta.created_at.strftime("%a %b %d %H:%M:%S %Z %Y") task2 = Task( batch=batch, completed=True, input_csv_fields={ 'number': '2', 'letter': 'b' }, ) task2.save() TaskAssignment(answers={ 'combined': '2b' }, assigned_to=user, completed=True, task=task2).save() csv_output = StringIO() batch.to_csv(csv_output) csv_string = csv_output.getvalue() self.assertTrue( '"Input.letter","Input.number","Answer.combined","Turkle.Username"\r\n' in csv_string) self.assertTrue('"b","2","2b","joe"\r\n' in csv_string) self.assertTrue('"a","1","1a","joe"\r\n' in csv_string) self.assertTrue(time_string in csv_string)
def test_expire_abandoned_assignments(self): t = timezone.now() dt = datetime.timedelta(hours=2) past = t - dt project = Project(login_required=False) project.save() batch = Batch(allotted_assignment_time=1, project=project) batch.save() task = Task(batch=batch) task.save() ha = TaskAssignment( completed=False, expires_at=past, task=task, ) # Bypass TaskAssignment's save(), which updates expires_at super(TaskAssignment, ha).save() self.assertEqual(TaskAssignment.objects.count(), 1) User.objects.create_superuser('admin', '*****@*****.**', 'secret') client = django.test.Client() client.login(username='******', password='******') response = client.get( reverse('turkle_admin:expire_abandoned_assignments')) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], '/admin/') self.assertEqual(TaskAssignment.objects.count(), 0) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), u'All 1 abandoned Tasks have been expired')
def test_return_task_assignment(self): user = User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment( assigned_to=user, task=self.task ) task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('return_task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index'))
def test_return_task_assignment(self): user = User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment(assigned_to=user, task=self.task) task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get( reverse('return_task_assignment', kwargs={ 'task_id': self.task.id, 'task_assignment_id': task_assignment.id })) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index'))
def test_project_to_csv(self): project = Project(name='test', html_template='<p>${number} - ${letter}</p>') project.save() batch_one = Batch(project=project) batch_one.save() task1 = Task( batch=batch_one, completed=True, input_csv_fields={ 'number': '1', 'letter': 'a' }, ) task1.save() TaskAssignment(answers={ 'combined': '1a' }, assigned_to=None, completed=True, task=task1).save() batch_two = Batch(project=project) batch_two.save() task2 = Task(batch=batch_two, completed=True, input_csv_fields={ 'number': '2', 'letter': 'b' }) task2.save() TaskAssignment(answers={ 'combined': '2b' }, assigned_to=None, completed=True, task=task2).save() csv_output = BytesIO() project.to_csv(csv_output) rows = csv_output.getvalue().split(b'\r\n') self.assertTrue( b'"Input.letter","Input.number","Answer.combined"' in rows[0]) self.assertTrue(any([b'"a","1","1a"' in row for row in rows[1:]])) self.assertTrue(any([b'"b","2","2b"' in row for row in rows[1:]]))
def handle(self, *args, **options): t0 = datetime.now() (total_deleted, _) = TaskAssignment.expire_all_abandoned() t = datetime.now() dt = (t - t0).total_seconds() logging.basicConfig(format="%(asctime)-15s %(message)s", level=logging.INFO) logging.info('TURKLE: Expired {0} abandoned Task Assignments in {1:.3f} seconds'. format(total_deleted, dt))
def test_accept_next_task__no_more_tasks(self): User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment(completed=True, task=self.task) task_assignment.save() self.assertEqual(self.task.taskassignment_set.count(), 1) client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) self.assertEqual(self.task.taskassignment_set.count(), 1) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), 'No more Tasks available for Batch {}'.format(self.batch.name))
def test_batch_to_csv(self): project = Project(name='test', html_template='<p>${number} - ${letter}</p>') project.save() batch = Batch(project=project) batch.save() task1 = Task( batch=batch, completed=True, input_csv_fields={ 'number': '1', 'letter': 'a' }, ) task1.save() TaskAssignment(answers={ 'combined': '1a' }, assigned_to=None, completed=True, task=task1).save() task2 = Task( batch=batch, completed=True, input_csv_fields={ 'number': '2', 'letter': 'b' }, ) task2.save() TaskAssignment(answers={ 'combined': '2b' }, assigned_to=None, completed=True, task=task2).save() csv_output = StringIO() batch.to_csv(csv_output) csv_string = csv_output.getvalue() self.assertTrue(b'"Input.letter","Input.number","Answer.combined"\r\n' in csv_string) self.assertTrue(b'"b","2","2b"\r\n' in csv_string) self.assertTrue(b'"a","1","1a"\r\n' in csv_string)
def expire_abandoned_assignments(request): """ Security behavior: - Access to this page is limited to requesters. """ (total_deleted, _) = TaskAssignment.expire_all_abandoned() messages.info(request, u'All {} abandoned Tasks have been expired'.format(total_deleted)) return redirect('/admin/turkle')
def accept_next_task(request, batch_id): """ Security behavior: - If the user does not have permission to access the Batch+Task, they are redirected to the index page with an error message. """ try: with transaction.atomic(): batch = Batch.objects.get(id=batch_id) # Lock access to all Tasks available to current user in the batch batch.available_task_ids_for(request.user).select_for_update() task_id = _skip_aware_next_available_task_id(request, batch) if task_id: ha = TaskAssignment() if request.user.is_authenticated: ha.assigned_to = request.user else: ha.assigned_to = None ha.task_id = task_id ha.save() except ObjectDoesNotExist: messages.error(request, u'Cannot find Task Batch with ID {}'.format(batch_id)) return redirect(index) if task_id: return redirect(task_assignment, task_id, ha.id) else: messages.error(request, u'No more Tasks available from Batch {}'.format(batch_id)) return redirect(index)
def test_project_to_csv_different_answers_per_batch(self): project = Project(name='test', html_template='<p>${letter}</p>') project.save() batch_one = Batch(project=project) batch_one.save() task1 = Task( batch=batch_one, completed=True, input_csv_fields={'letter': 'a'}, ) task1.save() TaskAssignment(answers={ '1': 1, '2': 2 }, assigned_to=None, completed=True, task=task1).save() batch_two = Batch(project=project) batch_two.save() task2 = Task( batch=batch_two, completed=True, input_csv_fields={'letter': 'b'}, ) task2.save() TaskAssignment(answers={ '3': 3, '4': 4 }, assigned_to=None, completed=True, task=task2).save() csv_output = BytesIO() project.to_csv(csv_output) rows = csv_output.getvalue().split(b'\r\n') self.assertTrue( b'"Input.letter","Answer.1","Answer.2","Answer.3","Answer.4"' in rows[0]) self.assertTrue(b'"a","1","2","",""' in rows[1]) self.assertTrue(b'"b","","","3","4"' in rows[2])
def test_accept_next_task__no_more_tasks(self): User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment(completed=True, task=self.task) task_assignment.save() self.assertEqual(self.task.taskassignment_set.count(), 1) client = django.test.Client() client.login(username='******', password='******') response = client.get( reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) self.assertEqual(self.task.taskassignment_set.count(), 1) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), 'No more Tasks available for Batch {}'.format(self.batch.name))
def test_batch_to_csv(self): template = '<p>${number} - ${letter}</p><textarea>' project = Project.objects.create(name='test', html_template=template) batch = Batch.objects.create(project=project) user = User.objects.create(username='******') task1 = Task( batch=batch, completed=True, input_csv_fields={'number': '1', 'letter': 'a'}, ) task1.save() ta = TaskAssignment( answers={'combined': '1a'}, assigned_to=user, completed=True, task=task1, ) ta.save() time_string = ta.created_at.strftime("%a %b %d %H:%M:%S %Z %Y") task2 = Task( batch=batch, completed=True, input_csv_fields={'number': '2', 'letter': 'b'}, ) task2.save() TaskAssignment( answers={'combined': '2b'}, assigned_to=user, completed=True, task=task2 ).save() csv_output = StringIO() batch.to_csv(csv_output) csv_string = csv_output.getvalue() self.assertTrue( '"Input.letter","Input.number","Answer.combined","Turkle.Username"\r\n' in csv_string) self.assertTrue('"b","2","2b","joe"\r\n' in csv_string) self.assertTrue('"a","1","1a","joe"\r\n' in csv_string) self.assertTrue(time_string in csv_string)
def test_return_task_assignment__anon_user_returns_other_users_task(self): user = User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment( assigned_to=user, task=self.task ) task_assignment.save() client = django.test.Client() response = client.get(reverse('return_task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), 'The Task you are trying to return belongs to another user')
def test_return_task_assignment__anon_user_returns_other_users_task(self): user = User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment(assigned_to=user, task=self.task) task_assignment.save() client = django.test.Client() response = client.get( reverse('return_task_assignment', kwargs={ 'task_id': self.task.id, 'task_assignment_id': task_assignment.id })) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), 'The Task you are trying to return belongs to another user')
def test_expire_all_abandoned__dont_delete_completed(self): t = timezone.now() dt = datetime.timedelta(hours=2) past = t - dt project = Project(login_required=False) project.save() batch = Batch(allotted_assignment_time=1, project=project) batch.save() task = Task(batch=batch) task.save() ha = TaskAssignment( completed=True, expires_at=past, task=task, ) # Bypass TaskAssignment's save(), which updates expires_at super(TaskAssignment, ha).save() self.assertEqual(TaskAssignment.objects.count(), 1) TaskAssignment.expire_all_abandoned() self.assertEqual(TaskAssignment.objects.count(), 1)
def test_return_completed_task_assignment(self): user = User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment( assigned_to=user, completed=True, task=self.task ) task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('return_task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), "The Task can't be returned because it has been completed")
def test_index_no_abandoned_assignments(self): TaskAssignment( assigned_to=None, completed=False, task=self.task, ).save() client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('index')) self.assertEqual(response.status_code, 200) self.assertFalse(b'You have abandoned' in response.content)
def test_available_tasks_for__aph_is_2(self): batch = Batch( assignments_per_task=2, project=self.project ) batch.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) task = Task( batch=batch, ) task.save() self.assertEqual(batch.total_available_tasks_for(self.user), 1) task_assignment = TaskAssignment( assigned_to=self.user, completed=False, task=task, ) task_assignment.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) other_user = User.objects.create_user('other_user', password='******') self.assertEqual(batch.total_available_tasks_for(other_user), 1) task_assignment = TaskAssignment( assigned_to=other_user, completed=False, task=task, ) task_assignment.save() self.assertEqual(batch.total_available_tasks_for(other_user), 0)
def setUp(self): project = Project(login_required=False, name='foo', html_template='<p></p><textarea>') project.save() batch = Batch(project=project) batch.save() self.task = Task(batch=batch, input_csv_fields='{}') self.task.save() self.task_assignment = TaskAssignment( assigned_to=None, completed=False, task=self.task ) self.task_assignment.save()
def test_return_completed_task_assignment(self): user = User.objects.create_user('testuser', password='******') task_assignment = TaskAssignment(assigned_to=user, completed=True, task=self.task) task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get( reverse('return_task_assignment', kwargs={ 'task_id': self.task.id, 'task_assignment_id': task_assignment.id })) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), "The Task can't be returned because it has been completed")
def test_expire_all_abandoned(self): t = timezone.now() dt = datetime.timedelta(hours=2) past = t - dt project = Project(login_required=False) project.save() batch = Batch( allotted_assignment_time=1, project=project ) batch.save() task = Task(batch=batch) task.save() ha = TaskAssignment( completed=False, expires_at=past, task=task, ) # Bypass TaskAssignment's save(), which updates expires_at super(TaskAssignment, ha).save() self.assertEqual(TaskAssignment.objects.count(), 1) TaskAssignment.expire_all_abandoned() self.assertEqual(TaskAssignment.objects.count(), 0)
def test_available_tasks_for__aph_is_1(self): batch = Batch( assignments_per_task=1, project=self.project ) batch.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) self.assertEqual(batch.next_available_task_for(self.user), None) task = Task( batch=batch, ) task.save() self.assertEqual(batch.total_available_tasks_for(self.user), 1) self.assertEqual(batch.next_available_task_for(self.user), task) task_assignment = TaskAssignment( assigned_to=self.user, completed=False, task=task, ) task_assignment.save() self.assertEqual(batch.total_available_tasks_for(self.user), 0) self.assertEqual(batch.next_available_task_for(self.user), None)
def test_index_abandoned_assignment_from_inactive_project(self): # Don't show abandoned tasks from inactive projects TaskAssignment( assigned_to=self.user, completed=False, task=self.task, ).save() self.batch.active = False self.batch.save() client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('index')) self.assertEqual(response.status_code, 200) self.assertFalse(b'You have abandoned' in response.content)
def accept_task(request, batch_id, task_id): """ Accept task from preview Security behavior: - If the user does not have permission to access the Batch+Task, they are redirected to the index page with an error message. """ try: batch = Batch.objects.get(id=batch_id) except ObjectDoesNotExist: messages.error(request, u'Cannot find Task Batch with ID {}'.format(batch_id)) return redirect(index) try: task = Task.objects.get(id=task_id) except ObjectDoesNotExist: messages.error(request, u'Cannot find Task with ID {}'.format(task_id)) return redirect(index) try: with transaction.atomic(): # Lock access to the specified Task Task.objects.filter(id=task_id).select_for_update() # Will throw ObjectDoesNotExist exception if Task no longer available batch.available_tasks_for(request.user).get(id=task_id) ha = TaskAssignment() if request.user.is_authenticated: ha.assigned_to = request.user else: ha.assigned_to = None ha.task = task ha.save() if request.user.is_authenticated: logger.info('User(%i) accepted Task(%i)', request.user.id, task.id) else: logger.info('Anonymous user accepted Task(%i)', task.id) except ObjectDoesNotExist: messages.error( request, u'The Task with ID {} is no longer available'.format(task_id)) return redirect(index) return redirect(task_assignment, task.id, ha.id)
def setUp(self): project = Project(name='foo', html_template='<p>${foo}: ${bar}</p>') project.save() self.batch = Batch(project=project, name='foo', filename='foo.csv') self.batch.save() task = Task(batch=self.batch, input_csv_fields={ 'foo': 'fufu', 'bar': 'baba' }) task.save() TaskAssignment(answers={ 'a1': 'sauce' }, assigned_to=None, completed=True, task=task).save()
def test_accept_claimed_task(self): User.objects.create_superuser('admin', '*****@*****.**', 'secret') other_user = User.objects.create_user('testuser', password='******') TaskAssignment(assigned_to=other_user, task=self.task).save() self.assertEqual(self.task.taskassignment_set.count(), 1) client = django.test.Client() client.login(username='******', password='******') response = client.get( reverse('accept_task', kwargs={ 'batch_id': self.batch.id, 'task_id': self.task.id })) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), 'The Task with ID {} is no longer available'.format(self.task.id))
def test_task_marked_as_completed(self): # When assignment_per_task==1, completing 1 Assignment marks Task as complete project = Project(name='test', html_template='<p>${number} - ${letter}</p>') project.save() batch = Batch(project=project) batch.save() task = Task(batch=batch, input_csv_fields={ 'number': '1', 'letter': 'a' }) task.save() self.assertEqual(batch.assignments_per_task, 1) self.assertFalse(task.completed) TaskAssignment(assigned_to=None, completed=True, task=task).save() task.refresh_from_db() self.assertTrue(task.completed)
def accept_task(request, batch_id, task_id): """ Accept task from preview Security behavior: - If the user does not have permission to access the Batch+Task, they are redirected to the index page with an error message. """ try: batch = Batch.objects.get(id=batch_id) except ObjectDoesNotExist: messages.error(request, u'Cannot find Task Batch with ID {}'.format(batch_id)) return redirect(index) try: task = Task.objects.get(id=task_id) except ObjectDoesNotExist: messages.error(request, u'Cannot find Task with ID {}'.format(task_id)) return redirect(index) try: with transaction.atomic(): # Lock access to the specified Task Task.objects.filter(id=task_id).select_for_update() # Will throw ObjectDoesNotExist exception if Task no longer available batch.available_tasks_for(request.user).get(id=task_id) ha = TaskAssignment() if request.user.is_authenticated: ha.assigned_to = request.user else: ha.assigned_to = None ha.task = task ha.save() if request.user.is_authenticated: logger.info('User(%i) accepted Task(%i)', request.user.id, task.id) else: logger.info('Anonymous user accepted Task(%i)', task.id) except ObjectDoesNotExist: messages.error(request, u'The Task with ID {} is no longer available'.format(task_id)) return redirect(index) return redirect(task_assignment, task.id, ha.id)
def accept_next_task(request, batch_id): """ Accept task from index or auto accept next task Security behavior: - If the user does not have permission to access the Batch+Task, they are redirected to the index page with an error message. """ try: with transaction.atomic(): batch = Batch.objects.get(id=batch_id) # Lock access to all Tasks available to current user in the batch batch.available_task_ids_for(request.user).select_for_update() task_id = _skip_aware_next_available_task_id(request, batch) if task_id: ha = TaskAssignment() if request.user.is_authenticated: ha.assigned_to = request.user else: ha.assigned_to = None ha.task_id = task_id ha.save() if request.user.is_authenticated: logger.info('User(%i) accepted Task(%i)', request.user.id, task_id) else: logger.info('Anonymous user accepted Task(%i)', task_id) except ObjectDoesNotExist: messages.error(request, u'Cannot find Task Batch with ID {}'.format(batch_id)) return redirect(index) if task_id: return redirect(task_assignment, task_id, ha.id) else: messages.error(request, u'No more Tasks available for Batch {}'.format(batch.name)) return redirect(index)
def test_skip_and_accept_next_task(self): client = django.test.Client() ha_one = TaskAssignment(task=self.task_one) ha_one.save() # Skip task_one response = client.post(reverse('skip_and_accept_next_task', kwargs={'batch_id': self.batch.id, 'task_id': self.task_one.id, 'task_assignment_id': ha_one.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) # Verify that task_one has been skipped response = client.get(reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) self.assertEqual(response.status_code, 302) self.assertTrue('{}/assignment/'.format(self.task_two.id) in response['Location']) # Skip task_two ha_two = self.task_two.taskassignment_set.first() response = client.post(reverse('skip_and_accept_next_task', kwargs={'batch_id': self.batch.id, 'task_id': self.task_two.id, 'task_assignment_id': ha_two.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) # Verify that task_two has been skipped response = client.get(reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) self.assertEqual(response.status_code, 302) self.assertTrue('{}/assignment/'.format(self.task_three.id) in response['Location']) # Skip task_three ha_three = self.task_three.taskassignment_set.first() response = client.post(reverse('skip_and_accept_next_task', kwargs={'batch_id': self.batch.id, 'task_id': self.task_three.id, 'task_assignment_id': ha_three.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) # Verify that, with all existing Tasks skipped, we have been redirected back to # task_one and that info message is displayed about only skipped Tasks remaining response = client.get(reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) self.assertEqual(response.status_code, 302) self.assertTrue('{}/assignment/'.format(self.task_one.id) in response['Location']) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), 'Only previously skipped Tasks are available') # Skip task_one for a second time ha_one = self.task_one.taskassignment_set.first() response = client.post(reverse('skip_and_accept_next_task', kwargs={'batch_id': self.batch.id, 'task_id': self.task_one.id, 'task_assignment_id': ha_one.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) # Verify that task_one has been skipped for a second time response = client.get(reverse('accept_next_task', kwargs={'batch_id': self.batch.id})) self.assertEqual(response.status_code, 302) self.assertTrue('{}/assignment/'.format(self.task_two.id) in response['Location'])
class TestTaskAssignmentIFrame(TestCase): def setUp(self): self.user = User.objects.create_user('testuser', password='******') self.admin = User.objects.create_superuser('admin', '*****@*****.**', 'secret') self.project = Project(name="Test", login_required=False) self.project.created_by = self.admin self.project.updated_by = self.admin self.project.save() batch = Batch(project=self.project) batch.save() self.task = Task(input_csv_fields={}, batch=batch) self.task.save() self.task_assignment = TaskAssignment(task=self.task) self.task_assignment.save() def test_get_task_assignment_iframe_with_wrong_user(self): User.objects.create_user('wrong_user', password='******') self.task_assignment.assigned_to = self.user self.task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get( reverse('task_assignment_iframe', kwargs={ 'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id })) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertTrue( 'You do not have permission to work on the Task Assignment with ID' in str(messages[0])) def test_template_with_submit_button(self): self.project.html_template = \ '<input id="my_submit_button" type="submit" value="MySubmit" />' save_model(self.project) self.project.refresh_from_db() self.assertTrue(self.project.html_template_has_submit_button) client = django.test.Client() response = client.get( reverse('task_assignment_iframe', kwargs={ 'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id })) self.assertEqual(response.status_code, 200) self.assertTrue(b'my_submit_button' in response.content) self.assertFalse(b'submitButton' in response.content) def test_template_without_submit_button(self): self.project.form = '<input id="foo" type="text" value="MyText" />' self.project.save() self.project.refresh_from_db() self.assertFalse(self.project.html_template_has_submit_button) client = django.test.Client() response = client.get( reverse('task_assignment_iframe', kwargs={ 'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id })) self.assertEqual(response.status_code, 200) self.assertFalse(b'my_submit_button' in response.content) self.assertTrue(b'submitButton' in response.content)
class TestTaskAssignment(TestCase): def setUp(self): project = Project(login_required=False, name='foo', html_template='<p></p><textarea>') project.save() batch = Batch(project=project) batch.save() self.task = Task(batch=batch, input_csv_fields='{}') self.task.save() self.task_assignment = TaskAssignment( assigned_to=None, completed=False, task=self.task ) self.task_assignment.save() def test_get_task_assignment(self): client = django.test.Client() response = client.get(reverse('task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 200) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 0) def test_get_task_assignment_with_bad_task_id(self): client = django.test.Client() response = client.get(reverse('task_assignment', kwargs={'task_id': 666, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), 'Cannot find Task with ID {}'.format(666)) def test_get_task_assignment_with_bad_task_assignment_id(self): client = django.test.Client() response = client.get(reverse('task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': 666})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), 'Cannot find Task Assignment with ID {}'.format(666)) def test_get_task_assignment_with_wrong_user(self): user = User.objects.create_user('testuser', password='******') User.objects.create_user('wrong_user', password='******') self.task_assignment.assigned_to = user self.task_assignment.save() client = django.test.Client() client.login(username='******', password='******') response = client.get(reverse('task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertTrue('You do not have permission to work on the Task Assignment with ID' in str(messages[0])) def test_get_task_assignment_owned_by_user_as_anonymous(self): user = User.objects.create_user('testuser', password='******') self.task_assignment.assigned_to = user self.task_assignment.save() client = django.test.Client() response = client.get(reverse('task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id})) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertTrue('You do not have permission to work on the Task Assignment with ID' in str(messages[0])) def test_submit_assignment_without_auto_accept(self): client = django.test.Client() s = client.session s.update({'auto_accept_status': False}) s.save() response = client.post(reverse('task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id}), {'foo': 'bar'}) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('index')) def test_submit_assignment_with_auto_accept(self): client = django.test.Client() s = client.session s.update({'auto_accept_status': True}) s.save() response = client.post(reverse('task_assignment', kwargs={'task_id': self.task.id, 'task_assignment_id': self.task_assignment.id}), {'foo': 'bar'}) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], reverse('accept_next_task', kwargs={'batch_id': self.task.batch_id}))
def expire_abandoned_assignments(self, request): (total_deleted, _) = TaskAssignment.expire_all_abandoned() messages.info(request, 'All {} abandoned Tasks have been expired'.format(total_deleted)) return redirect(reverse('turkle_admin:index'))
class TestModels(django.test.TestCase): def setUp(self): """ Sets up Project, Task objects, and saves them to the DB. The Project form HTML only displays the one input variable. The Task has inputs and answers and refers to the Project form. """ admin = User.objects.create_superuser('admin', '*****@*****.**', 'secret') project = Project(name='test', html_template="<p>${foo}</p><textarea>") project.created_by = admin project.updated_by = admin save_model(project) batch = Batch(name='test', project=project, filename='test.csv') batch.created_by = admin batch.updated_by = admin save_model(batch) task = Task( batch=batch, input_csv_fields={'foo': 'bar'}, completed=True, ) task.save() self.task = task self.task_assignment = TaskAssignment( answers={ "comment": "\u221e", "userDisplayLanguage": "", "sentence_textbox_3_verb1": "", "city": "", "sentence_textbox_1_verb6": "", "sentence_textbox_1_verb7": "", "sentence_textbox_1_verb4": "", "sentence_textbox_1_verb5": "", "sentence_textbox_1_verb2": "", "sentence_textbox_1_verb3": "", "sentence_textbox_1_verb1": "", "sentence_textbox_2_verb4": "", "csrfmiddlewaretoken": "7zxQ9Yyug6Nsnm4nLky9p8ObJwNipdu8", "sentence_drop_2_verb3": "foo", "sentence_drop_2_verb2": "foo", "sentence_drop_2_verb1": "foo", "sentence_textbox_2_verb1": "", "sentence_textbox_2_verb3": "", "sentence_drop_2_verb4": "foo", "sentence_textbox_2_verb2": "", "submitit": "Submit", "browserInfo": "", "sentence_drop_1_verb1": "foo", "sentence_drop_1_verb2": "foo", "sentence_drop_1_verb3": "foo", "sentence_drop_1_verb4": "foo", "sentence_drop_1_verb5": "foo", "sentence_drop_1_verb6": "foo", "sentence_drop_1_verb7": "foo", "country": "", "sentence_drop_3_verb1": "foo", "ipAddress": "", "region": "" }, assigned_to=None, completed=True, task=task ) self.task_assignment.save() def test_extract_fieldnames_from_form_html(self): self.assertEqual( {'foo': True}, self.task.batch.project.fieldnames ) project = Project(name='test', html_template='<p>${foo} - ${bar}</p><textarea>') project.clean() self.assertEqual( {'foo': True, 'bar': True}, project.fieldnames ) def test_project_to_csv(self): project = Project(name='test', html_template='<p>${number} - ${letter}</p><textarea>') project.save() batch_one = Batch(project=project) batch_one.save() task1 = Task( batch=batch_one, completed=True, input_csv_fields={'number': '1', 'letter': 'a'}, ) task1.save() TaskAssignment( answers={'combined': '1a'}, assigned_to=None, completed=True, task=task1 ).save() batch_two = Batch(project=project) batch_two.save() task2 = Task( batch=batch_two, completed=True, input_csv_fields={'number': '2', 'letter': 'b'} ) task2.save() TaskAssignment( answers={'combined': '2b'}, assigned_to=None, completed=True, task=task2 ).save() csv_output = StringIO() project.to_csv(csv_output) rows = csv_output.getvalue().split('\r\n') self.assertTrue( '"Input.letter","Input.number","Answer.combined"' in rows[0]) self.assertTrue(any(['"a","1","1a"' in row for row in rows[1:]])) self.assertTrue(any(['"b","2","2b"' in row for row in rows[1:]])) def test_project_to_csv_different_answers_per_batch(self): project = Project(name='test', html_template='<p>${letter}</p><textarea>') project.save() batch_one = Batch(project=project) batch_one.save() task1 = Task( batch=batch_one, completed=True, input_csv_fields={'letter': 'a'}, ) task1.save() TaskAssignment( answers={'1': 1, '2': 2}, assigned_to=None, completed=True, task=task1 ).save() batch_two = Batch(project=project) batch_two.save() task2 = Task( batch=batch_two, completed=True, input_csv_fields={'letter': 'b'}, ) task2.save() TaskAssignment( answers={'3': 3, '4': 4}, assigned_to=None, completed=True, task=task2 ).save() csv_output = StringIO() project.to_csv(csv_output) rows = csv_output.getvalue().split('\r\n') self.assertTrue( '"Input.letter","Answer.1","Answer.2","Answer.3","Answer.4"' in rows[0]) self.assertTrue('"a","1","2","",""' in rows[1]) self.assertTrue('"b","","","3","4"' in rows[2]) def test_new_task(self): """ str(task) should return the template's title followed by :id of the task. """ self.assertEqual('Task id:{}'.format(self.task.id), str(self.task)) def test_result_to_dict_Answer(self): self.assertEqual( 'foo', self.task_assignment.answers['sentence_drop_1_verb1'] ) def test_result_to_dict_ignore_csrfmiddlewaretoken(self): with self.assertRaises(KeyError): self.task_assignment.answers['Answer.csrfmiddlewaretoken'] def test_result_to_dict_should_include_inputs(self): self.assertEqual( 'foo', self.task_assignment.answers['sentence_drop_1_verb1'] ) def test_result_to_dict_unicode(self): self.assertEqual( '∞', self.task_assignment.answers['comment'] )