def test_api_rs_question_list_permissions(client): rs_set = TwoReaderStudies() q1, q2 = ( QuestionFactory(reader_study=rs_set.rs1), QuestionFactory(reader_study=rs_set.rs2), ) tests = ( (None, 401, []), (rs_set.creator, 200, []), (rs_set.editor1, 200, [q1.pk]), (rs_set.reader1, 200, [q1.pk]), (rs_set.editor2, 200, [q2.pk]), (rs_set.reader2, 200, [q2.pk]), (rs_set.u, 200, []), ) for test in tests: response = get_view_for_user( viewname="api:reader-studies-question-list", client=client, user=test[0], content_type="application/json", ) assert response.status_code == test[1] if test[1] != 401: # We provided auth details and get a response assert response.json()["count"] == len(test[2]) pks = [obj["pk"] for obj in response.json()["results"]] for pk in test[2]: assert str(pk) in pks
def test_mine(client): im1, im2 = ImageFactory(), ImageFactory() rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory() rs1.images.add(im1) rs2.images.add(im2) reader = UserFactory() rs1.add_reader(reader) rs2.add_reader(reader) q1 = QuestionFactory( reader_study=rs1, answer_type=Question.ANSWER_TYPE_BOOL ) q2 = QuestionFactory( reader_study=rs2, answer_type=Question.ANSWER_TYPE_BOOL ) a1 = AnswerFactory(question=q1, creator=reader, answer=True) a1.images.add(im1) a2 = AnswerFactory(question=q2, creator=reader, answer=True) a2.images.add(im2) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, content_type="application/json", ) response = response.json() assert response["count"] == 2 response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, data={"question__reader_study": rs1.pk}, content_type="application/json", ) response = response.json() assert response["count"] == 1 assert response["results"][0]["pk"] == str(a1.pk) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, data={"question__reader_study": rs2.pk}, content_type="application/json", ) response = response.json() assert response["count"] == 1 assert response["results"][0]["pk"] == str(a2.pk)
def test_assign_score(settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory(use_display_sets=False) im = ImageFactory() q1 = QuestionFactory(reader_study=rs) q2 = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.MULTIPLE_CHOICE) e, r1, r2 = UserFactory(), UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(e) rs.add_reader(r1) rs.add_reader(r2) with capture_on_commit_callbacks(execute=True): a1 = AnswerFactory(question=q1, creator=r1, answer="foo") a1.images.add(im) assert a1.score is None with capture_on_commit_callbacks(execute=True): gt = AnswerFactory(question=q1, creator=e, answer="foo", is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 with capture_on_commit_callbacks(execute=True): a2 = AnswerFactory(question=q1, creator=r2, answer="foo") a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0 with capture_on_commit_callbacks(execute=True): a1 = AnswerFactory(question=q2, creator=r1, answer=[]) a1.images.add(im) a1.refresh_from_db() assert a1.score is None with capture_on_commit_callbacks(execute=True): gt = AnswerFactory(question=q2, creator=e, answer=[], is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 with capture_on_commit_callbacks(execute=True): a2 = AnswerFactory(question=q2, creator=r2, answer=[]) a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0
def test_api_rs_answer_mine_list_permissions(client): """ For the "mine" endpoint the list should be filtered by the users own answers """ rs_set = TwoReaderStudies() q1, q2 = ( QuestionFactory(reader_study=rs_set.rs1), QuestionFactory(reader_study=rs_set.rs2), ) reader11 = UserFactory() rs_set.rs1.add_reader(reader11) a1, a11, a2 = ( AnswerFactory(question=q1, creator=rs_set.reader1, answer=""), AnswerFactory(question=q1, creator=reader11, answer=""), AnswerFactory(question=q2, creator=rs_set.reader2, answer=""), ) tests = ( (None, 401, []), (rs_set.creator, 200, []), (rs_set.editor1, 200, []), (rs_set.reader1, 200, [a1.pk]), (reader11, 200, [a11.pk]), (rs_set.editor2, 200, []), (rs_set.reader2, 200, [a2.pk]), (rs_set.u, 200, []), ) for test in tests: response = get_view_for_user( viewname="api:reader-studies-answer-mine", client=client, user=test[0], content_type="application/json", ) assert response.status_code == test[1] if test[1] != 401: # We provided auth details and get a response assert response.json()["count"] == len(test[2]) pks = [obj["pk"] for obj in response.json()["results"]] for pk in test[2]: assert str(pk) in pks
def test_csv_export(now, client, answer_type, answer): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() editor = UserFactory() rs.add_editor(editor) reader = UserFactory() rs.add_reader(reader) q = QuestionFactory( question_text="foo", reader_study=rs, answer_type=answer_type ) a = AnswerFactory(question=q, answer=answer) a.images.add(im) a.save() response = get_view_for_user( viewname="api:reader-study-export-answers", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.get, content_type="application/json", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers assert ( f'filename="{rs.slug}-answers-2020-01-01T00:00:00+00:00.csv"' in headers ) assert a.question.question_text in content assert a.question.get_answer_type_display() in content assert str(a.question.required) in content assert a.question.get_image_port_display() in content if isinstance(answer, dict): for key in answer: assert key in content else: assert re.sub(r"[\n\r\t]", " ", str(a.answer)) in content assert im.name in content assert a.creator.username in content response = get_view_for_user( viewname="api:reader-study-export-answers", reverse_kwargs={"pk": rs.pk}, user=reader, client=client, method=client.get, content_type="application/json", ) assert response.status_code == 404
def test_answer_creator_is_reader(client): rs_set = TwoReaderStudies() im = ImageFactory() rs_set.rs1.images.add(im) q = QuestionFactory(reader_study=rs_set.rs1, answer_type=Question.AnswerType.BOOL) tests = ( (rs_set.editor1, 201), (rs_set.reader1, 201), (rs_set.editor2, 400), (rs_set.reader2, 400), (rs_set.u, 400), ) for test in tests: response = get_view_for_user( viewname="api:reader-studies-answer-list", user=test[0], client=client, method=client.post, data={ "answer": True, "images": [im.api_url], "question": q.api_url, }, content_type="application/json", ) assert response.status_code == test[1]
def test_reader_study_add_ground_truth_ds(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory(use_display_sets=True) QuestionFactory( reader_study=rs, question_text="bar", answer_type=Question.AnswerType.SINGLE_LINE_TEXT, ) civ = ComponentInterfaceValueFactory(image=ImageFactory()) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) editor = UserFactory() rs.editors_group.user_set.add(editor) gt = io.StringIO() fake_writer = csv.writer(gt) fake_writer.writerows([["images", "foo"], [str(ds.pk), "bar"]]) gt.seek(0) response = get_view_for_user( viewname="reader-studies:add-ground-truth", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"ground_truth": gt}, follow=True, user=editor, ) assert response.status_code == 200
def test_assert_modification_allowed(): rs = ReaderStudyFactory(use_display_sets=False) ci = ComponentInterfaceFactory( kind=InterfaceKind.InterfaceKindChoices.BOOL) civ = ComponentInterfaceValueFactory(interface=ci, value=True) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) del ds.is_editable civ2 = ComponentInterfaceValueFactory(interface=ci, value=True) ds.values.remove(civ) ds.values.add(civ2) assert ds.values.count() == 1 assert ds.values.first() == civ2 q = QuestionFactory(reader_study=rs) AnswerFactory(question=q, display_set=ds) del ds.is_editable with pytest.raises(ValidationError): with transaction.atomic(): ds.values.remove(civ2) assert ds.values.count() == 1 assert ds.values.first() == civ2
def test_answer_create(client): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": True, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == 201 answer = Answer.objects.get(pk=response.data.get("pk")) assert answer.creator == reader assert answer.images.count() == 1 assert answer.images.all()[0] == im assert answer.question == q assert answer.answer is True
def test_answer_is_correct_type(client, answer_type, answer, expected): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=answer_type) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": answer, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == expected
def test_ground_truth_is_excluded(client): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) editor = UserFactory() rs.add_editor(editor) rs.add_reader(editor) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) a1 = AnswerFactory(question=q, creator=editor, answer=True, is_ground_truth=True) a1.images.add(im) a2 = AnswerFactory(question=q, creator=editor, answer=True, is_ground_truth=False) a2.images.add(im) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=editor, client=client, method=client.get, content_type="application/json", ) results = response.json()["results"] assert len(results) == 1 assert results[0]["pk"] == str(a2.pk)
def test_api_rs_answer_detail_permissions(client): rs_set = TwoReaderStudies() q1 = QuestionFactory(reader_study=rs_set.rs1) reader11 = UserFactory() rs_set.rs1.add_reader(reader11) a1 = AnswerFactory(question=q1, creator=rs_set.reader1, answer="") tests = ( (None, 401), (rs_set.creator, 404), (rs_set.editor1, 200), (rs_set.reader1, 200), (reader11, 404), (rs_set.editor2, 404), (rs_set.reader2, 404), (rs_set.u, 404), ) for test in tests: response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": a1.pk}, client=client, user=test[0], content_type="application/json", ) assert response.status_code == test[1]
def test_assign_score(settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory() im = ImageFactory() q1 = QuestionFactory(reader_study=rs) q2 = QuestionFactory( reader_study=rs, answer_type=Question.ANSWER_TYPE_MULTIPLE_CHOICE ) e, r1, r2 = UserFactory(), UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(e) rs.add_reader(r1) rs.add_reader(r2) a1 = AnswerFactory(question=q1, creator=r1, answer="foo") a1.images.add(im) assert a1.score is None gt = AnswerFactory( question=q1, creator=e, answer="foo", is_ground_truth=True ) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 a2 = AnswerFactory(question=q1, creator=r2, answer="foo") a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0 a1 = AnswerFactory(question=q2, creator=r1, answer=[]) a1.images.add(im) assert a1.score is None gt = AnswerFactory(question=q2, creator=e, answer=[], is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 a2 = AnswerFactory(question=q2, creator=r2, answer=[]) a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0
def test_api_list_is_filtered(client): rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory() rs1_editor = UserFactory() rs1.add_editor(rs1_editor) q1, q2 = ( QuestionFactory(reader_study=rs1), QuestionFactory(reader_study=rs2), ) a1, _ = ( AnswerFactory(question=q1, answer=True), AnswerFactory(question=q2, answer=False), ) response = get_view_for_user(viewname="api:reader-study-list", user=rs1_editor, client=client) assert response.status_code == 200 assert response.json()["count"] == 1 response = get_view_for_user( viewname="api:reader-study-detail", reverse_kwargs={"pk": rs1.pk}, user=rs1_editor, client=client, ) assert response.status_code == 200 assert len(response.json()["questions"]) == 1 response = get_view_for_user( viewname="api:reader-studies-question-list", user=rs1_editor, client=client, ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(q1.pk) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=rs1_editor, client=client, ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(a1.pk)
def test_only_non_required_can_be_null(client, answer_type): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=answer_type, required=True) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": None, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == 400 q = QuestionFactory(reader_study=rs, answer_type=answer_type, required=False) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": None, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == 201
def test_question_delete_disabled_for_questions_with_answers(client): rs = ReaderStudyFactory(use_display_sets=False) r1, editor = UserFactory(), UserFactory() rs.add_reader(r1) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.AnswerType.BOOL, ) AnswerFactory(creator=r1, question=q, answer=True) assert Answer.objects.count() == 1 assert Question.objects.count() == 1 assert not q.is_fully_editable response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 403 assert Question.objects.count() == 1 # if answer is deleted, deletion of the question is possible again get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=editor, ) assert Answer.objects.count() == 0 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 302 assert Question.objects.count() == 0
def reader_study_with_mc_gt(reader_study_with_gt): rs = reader_study_with_gt q_choice = QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.CHOICE, question_text="C", ) q_multiple_choice = QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.MULTIPLE_CHOICE, question_text="MC", ) c_options = [ CategoricalOptionFactory(question=q_choice, title="fee"), CategoricalOptionFactory(question=q_choice, title="foh"), CategoricalOptionFactory(question=q_choice, title="fum"), ] mc_options = [ CategoricalOptionFactory(question=q_multiple_choice, title="fee"), CategoricalOptionFactory(question=q_multiple_choice, title="foh"), CategoricalOptionFactory(question=q_multiple_choice, title="fum"), ] editor = rs.editors_group.user_set.first() images = reader_study_with_gt.images.all() for question, answer in [ (q_choice, c_options[0].id), (q_multiple_choice, [mc_options[0].id, mc_options[1].id]), ]: ans = AnswerFactory( question=question, creator=editor, answer=answer, is_ground_truth=True, ) for im in images: ans.images.add(im) return rs
def reader_study_with_gt(): rs = ReaderStudyFactory() im1, im2 = ImageFactory(name="im1"), ImageFactory(name="im2") q1, q2, q3 = [ QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q1", ), QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q2", ), QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q3", ), ] r1, r2, editor = UserFactory(), UserFactory(), UserFactory() rs.add_reader(r1) rs.add_reader(r2) rs.add_editor(editor) rs.images.set([im1, im2]) rs.hanging_list = [{"main": im1.name}, {"main": im2.name}] rs.save() for question in [q1, q2, q3]: for im in [im1, im2]: ans = AnswerFactory( question=question, creator=editor, answer=True, is_ground_truth=True, ) ans.images.add(im) return rs
def test_assign_answer_image(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory() im = ImageFactory() editor, reader = UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(editor) rs.add_reader(reader) question = QuestionFactory( reader_study=rs, answer_type=Question.ANSWER_TYPE_POLYGON_IMAGE ) us = RawImageUploadSessionFactory(creator=reader) answer = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us.pk)}, ) f = StagedFileFactory( file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha" ) RawImageFileFactory(upload_session=us, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us.pk}, user=reader, client=client, method=client.patch, data={"answer": str(answer.pk)}, content_type="application/json", ) assert response.status_code == 200 answer.refresh_from_db() image = us.image_set.first() assert answer.answer_image == image assert reader.has_perm("view_image", image) assert editor.has_perm("view_image", image)
def test_read_only_fields(): rs = ReaderStudyFactory(use_display_sets=False) q = QuestionFactory(reader_study=rs) assert q.is_fully_editable is True assert q.read_only_fields == [] AnswerFactory(question=q, answer="true") assert q.is_fully_editable is False assert q.read_only_fields == [ "question_text", "answer_type", "image_port", "required", ]
def test_validate_answer(): u = UserFactory() im1, im2, im3 = ImageFactory(), ImageFactory(), ImageFactory() rs = ReaderStudyFactory(hanging_list=[ { "main": im1.name, "main-overlay": im3.name }, { "main": im2.name, "main-overlay": im3.name }, ]) rs.images.set([im1, im2, im3]) rs.add_reader(u) q = QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q1", ) answer = AnswerFactory( creator=u, question=q, answer=True, ) answer.images.set([im1, im3]) with pytest.raises(ValidationError) as e: Answer.validate( creator=u, question=q, answer=True, images=[im1, im3], ) assert ( e.value.message == f"User {u} has already answered this question for this set of images." ) assert (Answer.validate( creator=u, question=q, answer=True, images=[im2, im3], ) is None)
def test_answer_remove(client): rs = ReaderStudyFactory() r1, r2, editor = UserFactory(), UserFactory(), UserFactory() rs.add_reader(r1) rs.add_reader(r2) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.ANSWER_TYPE_BOOL, ) im = ImageFactory() a1 = AnswerFactory(creator=r1, question=q, answer=True) a1.images.set([im]) a2 = AnswerFactory(creator=r2, question=q, answer=True) a2.images.set([im]) assert Answer.objects.count() == 2 response = get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=r1, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=editor, ) assert response.status_code == 200 assert Answer.objects.count() == 1 assert Answer.objects.filter(creator=r1).count() == 0 assert Answer.objects.filter(creator=r2).count() == 1
def test_upload_session_owned_by_answer_creator(client, settings, answer_type): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory() im = ImageFactory() editor, reader = UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(editor) rs.add_reader(reader) question = QuestionFactory(reader_study=rs, answer_type=answer_type) us1 = RawImageUploadSessionFactory(creator=reader) us2 = RawImageUploadSessionFactory(creator=editor) answer1 = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us1.pk)}, ) f = StagedFileFactory(file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha") RawImageFileFactory(upload_session=us1, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us2.pk}, user=editor, client=client, method=client.patch, data={"answer": str(answer1.pk)}, content_type="application/json", ) assert response.status_code == 400 assert (b"User does not have permission to add an image to this answer" in response.rendered_content)
def test_question_delete(client): rs = ReaderStudyFactory(use_display_sets=False) r1, editor = UserFactory(), UserFactory() rs.add_reader(r1) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.AnswerType.BOOL, ) assert Question.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, follow=True, user=r1, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 302 assert Question.objects.count() == 0 assert str(rs) in response.url
def test_question_accepts_image_type_answers(client, settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory() im = ImageFactory() reader = UserFactory() rs.images.add(im) rs.add_reader(reader) question = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) us = RawImageUploadSessionFactory(creator=reader) answer = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us.pk)}, ) f = StagedFileFactory(file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha") RawImageFileFactory(upload_session=us, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us.pk}, user=reader, client=client, method=client.patch, data={"answer": str(answer.pk)}, content_type="application/json", ) assert response.status_code == 400 assert (b"This question does not accept image type answers" in response.rendered_content)
def test_api_rs_question_detail_permissions(client): rs_set = TwoReaderStudies() q1 = QuestionFactory(reader_study=rs_set.rs1) tests = ( (None, 404), (rs_set.creator, 404), (rs_set.editor1, 200), (rs_set.reader1, 200), (rs_set.editor2, 404), (rs_set.reader2, 404), (rs_set.u, 404), ) for test in tests: response = get_view_for_user( viewname="api:reader-studies-question-detail", reverse_kwargs={"pk": q1.pk}, client=client, user=test[0], content_type="application/json", ) assert response.status_code == test[1]
def test_example_ground_truth(client, tmpdir): rs = ReaderStudyFactory() reader, editor = UserFactory(), UserFactory() q1, q2, q3 = ( QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.ANSWER_TYPE_BOOL, ), QuestionFactory( reader_study=rs, question_text="q2", answer_type=Question.ANSWER_TYPE_CHOICE, ), QuestionFactory( reader_study=rs, question_text="q3", answer_type=Question.ANSWER_TYPE_SINGLE_LINE_TEXT, ), ) CategoricalOptionFactory(question=q2, title="option") im1, im2, im3 = (ImageFactory(), ImageFactory(), ImageFactory()) rs.images.set([im1, im2, im3]) rs.add_reader(reader) rs.add_editor(editor) rs.generate_hanging_list() response = get_view_for_user( viewname="reader-studies:example-ground-truth", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=reader, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:example-ground-truth", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert Answer.objects.count() == 0 gt = io.BytesIO() gt.write(response.content) gt.seek(0) response = get_view_for_user( viewname="reader-studies:add-ground-truth", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, follow=True, data={"ground_truth": gt}, user=editor, ) assert response.status_code == 200 assert Answer.objects.count() == rs.images.count() * rs.questions.count() for image in [im1, im2, im3]: for question in [q1, q2, q3]: assert Answer.objects.filter(images=image, question=question, is_ground_truth=True).exists()
def test_answer_update(client): im1, im2 = ImageFactory(), ImageFactory() rs = ReaderStudyFactory() rs.images.add(im1, im2) rs.save() reader = UserFactory() rs.add_reader(reader) editor = UserFactory() rs.add_editor(editor) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": True, "images": [im1.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == 201 answer = Answer.objects.get(pk=response.data.get("pk")) assert answer.answer is True assert answer.images.first() == im1 assert answer.history.count() == 1 response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=reader, client=client, method=client.patch, data={ "answer": False, "images": [im2.api_url] }, content_type="application/json", ) assert response.status_code == 400 answer.refresh_from_db() assert response.json() == { "non_field_errors": ["This reader study does not allow answer modification."] } assert answer.answer is True assert answer.images.first() == im1 assert answer.history.count() == 1 rs.allow_answer_modification = True rs.save() response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=reader, client=client, method=client.patch, data={ "answer": False, "images": [im2.api_url] }, content_type="application/json", ) assert response.status_code == 400 answer.refresh_from_db() assert response.json() == { "non_field_errors": ["Only the answer field can be modified."] } assert answer.answer is True assert answer.images.first() == im1 assert answer.history.count() == 1 response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=reader, client=client, method=client.patch, data={"answer": False}, content_type="application/json", ) assert response.status_code == 200 answer.refresh_from_db() assert answer.answer is False assert answer.images.first() == im1 assert answer.history.count() == 2 response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=editor, client=client, method=client.patch, data={"answer": False}, content_type="application/json", ) assert response.status_code == 403 answer.refresh_from_db() assert answer.answer is False assert answer.history.count() == 2
def test_csv_export(client, answer_type, answer): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() editor = UserFactory() rs.add_editor(editor) reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(question_text="foo", reader_study=rs, answer_type=answer_type) a = AnswerFactory(question=q, answer=answer) a.images.add(im) a.save() response = get_view_for_user( viewname="api:reader-studies-answer-list", params={"question__reader_study": str(rs.pk)}, user=editor, client=client, method=client.get, HTTP_ACCEPT="text/csv", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers if isinstance(answer, dict): for key in answer: assert key in content else: assert re.sub(r"\n", r"\\n", str(a.answer)) in content assert a.creator.username in content response = get_view_for_user( viewname="api:reader-studies-question-list", params={"reader_study": str(rs.pk)}, user=editor, client=client, method=client.get, HTTP_ACCEPT="text/csv", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers assert a.question.question_text in content assert a.question.get_answer_type_display() in content assert str(a.question.required) in content assert a.question.get_image_port_display() in content response = get_view_for_user( viewname="api:image-list", params={"readerstudies": str(rs.pk)}, user=editor, client=client, method=client.get, HTTP_ACCEPT="text/csv", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers assert im.name in content
def test_ground_truth(client): rs = ReaderStudyFactory(is_educational=True) reader = UserFactory() rs.add_reader(reader) q1 = QuestionFactory(answer_type=Question.AnswerType.CHOICE, reader_study=rs) q2 = QuestionFactory(answer_type=Question.AnswerType.MULTIPLE_CHOICE, reader_study=rs) q3 = QuestionFactory( answer_type=Question.AnswerType.MULTIPLE_CHOICE_DROPDOWN, reader_study=rs, ) op1 = CategoricalOptionFactory(question=q1, title="option1") op2 = CategoricalOptionFactory(question=q2, title="option1") op3 = CategoricalOptionFactory(question=q2, title="option1") op4 = CategoricalOptionFactory(question=q3, title="option1") op5 = CategoricalOptionFactory(question=q3, title="option1") im = ImageFactory() rs.images.add(im) a1 = AnswerFactory(question=q1, answer=op1.pk, is_ground_truth=True) a1.images.add(im) a2 = AnswerFactory(question=q2, answer=[op2.pk, op3.pk], is_ground_truth=True) a2.images.add(im) a3 = AnswerFactory(question=q3, answer=[op4.pk, op5.pk], is_ground_truth=True) a3.images.add(im) response = get_view_for_user( viewname="api:reader-study-ground-truth", reverse_kwargs={ "pk": rs.pk, "case_pk": im.pk }, user=reader, client=client, content_type="application/json", follow=True, ) assert response.status_code == 200 response = response.json() assert response[str(q1.pk)] == { "answer": op1.pk, "answer_text": op1.title, "question_text": q1.question_text, "options": { str(op1.pk): op1.title }, "explanation": "", } assert response[str(q2.pk)] == { "answer": [op2.pk, op3.pk], "answer_text": f"{op2.title}, {op3.title}", "question_text": q2.question_text, "options": { str(op2.pk): op2.title, str(op3.pk): op3.title }, "explanation": "", } assert response[str(q3.pk)] == { "answer": [op4.pk, op5.pk], "answer_text": f"{op4.title}, {op5.title}", "question_text": q3.question_text, "options": { str(op4.pk): op4.title, str(op5.pk): op5.title }, "explanation": "", }