From d47aaa1605d220bb9d60073da973a30f68807098 Mon Sep 17 00:00:00 2001 From: Dan LaManna Date: Fri, 2 Jan 2026 09:10:50 -0500 Subject: [PATCH] Fix support for number question types --- isic/studies/tests/test_views.py | 59 +++++++++++++++++++++++++++++++- isic/studies/views.py | 17 ++++++--- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/isic/studies/tests/test_views.py b/isic/studies/tests/test_views.py index 7d043ed0..5c8e1078 100644 --- a/isic/studies/tests/test_views.py +++ b/isic/studies/tests/test_views.py @@ -1,7 +1,15 @@ from django.urls.base import reverse +from django.utils import timezone import pytest -from isic.studies.tests.factories import QuestionFactory, ResponseFactory, StudyFactory +from isic.studies.models import Question +from isic.studies.tests.factories import ( + AnnotationFactory, + QuestionFactory, + ResponseFactory, + StudyFactory, + StudyTaskFactory, +) @pytest.mark.django_db @@ -14,3 +22,52 @@ def test_study_responses_csv(staff_client) -> None: r = staff_client.get(reverse("study-download-responses", args=[study.pk])) assert r.status_code == 200 assert len(r.content.decode().splitlines()) == 3 + + +@pytest.mark.django_db +def test_study_responses_csv_number_question(staff_client) -> None: + question = QuestionFactory.create(type=Question.QuestionType.NUMBER, choices=[]) + study = StudyFactory.create(public=False, questions=[question]) + annotation = AnnotationFactory.create(study=study) + annotation.responses.create(question=question, value=42.5) + + r = staff_client.get(reverse("study-download-responses", args=[study.pk])) + assert r.status_code == 200 + lines = r.content.decode().splitlines() + assert len(lines) == 2 + assert "42.5" in lines[1] + + +@pytest.mark.django_db +@pytest.mark.parametrize( + ("input_value", "expected_value"), + [ + ("42", 42), + ("42.5", 42.5), + ("0", 0), + ("-5", -5), + ("-3.14", -3.14), + ], +) +def test_study_task_detail_post_number_question(client, input_value, expected_value): + question = QuestionFactory.create(type=Question.QuestionType.NUMBER, choices=[]) + study = StudyFactory.create( + questions=[question], + questions__required=True, + ) + + study_task = StudyTaskFactory.create(study=study) + user = study_task.annotator + client.force_login(user) + client.post( + reverse("study-task-detail", args=[study_task.pk]), + {"start_time": timezone.now(), question.pk: input_value}, + ) + assert study_task.annotation + + response = study_task.annotation.responses.first() + assert response.annotation.annotator == user + assert response.question == question + assert response.choice is None + assert response.value == expected_value + assert type(response.value) is type(expected_value) diff --git a/isic/studies/views.py b/isic/studies/views.py index 431260cc..5ef03cc2 100644 --- a/isic/studies/views.py +++ b/isic/studies/views.py @@ -337,12 +337,21 @@ def study_task_detail(request, pk): del form.cleaned_data["start_time"] # TODO: markups, one day? - for question_pk, choice_pk in form.cleaned_data.items(): - if choice_pk == "": # ignore optional questions + for question_pk, response_value in form.cleaned_data.items(): + if response_value == "": continue question = form.questions[int(question_pk)] - choices = {x.pk: x for x in question.choices.all()} - annotation.responses.create(question=question, choice=choices[int(choice_pk)]) + if question.type == Question.QuestionType.NUMBER: + annotation.responses.create( + question=question, + value=int(response_value) + if float(response_value).is_integer() + else float(response_value), + ) + else: + choices = {x.pk: x for x in question.choices.all()} + choice = choices[int(response_value)] + annotation.responses.create(question=question, choice=choice) return maybe_redirect_to_next_study_task(request, study_task.study) else: