Repository: comic/grand-challenge.org

Issue:
Question widget and validation settings are not copied when copying a reader study
**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://grand-challenge.org/reader-studies/cirruscoreweb-release-display-sets-editable/
2. Click on `Copy`
3. Make a copy with all options selected
4. The copied reader study has all the questions, but not the number widgets or validation settings.

**Expected behavior**
I expect widget, min, max and step settings to be copied to the new reader study

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -655,6 +655,13 @@ def form_valid(self, form):  # noqa: C901
                     interface=question.interface,
                     look_up_table=question.look_up_table,
                     overlay_segments=question.overlay_segments,
+                    widget=question.widget,
+                    answer_max_value=question.answer_max_value,
+                    answer_min_value=question.answer_min_value,
+                    answer_step_size=question.answer_step_size,
+                    answer_min_length=question.answer_min_length,
+                    answer_max_length=question.answer_max_length,
+                    answer_match_pattern=question.answer_match_pattern,
                 )
                 for option in question.options.all():
                     CategoricalOption.objects.create(


Test patch:
diff --git a/app/tests/reader_studies_tests/test_forms.py b/app/tests/reader_studies_tests/test_forms.py
--- a/app/tests/reader_studies_tests/test_forms.py
+++ b/app/tests/reader_studies_tests/test_forms.py
@@ -544,7 +544,7 @@ def test_reader_study_copy(
         reader_study=rs,
         question_text="question 1",
         help_text="Some help text",
-        answer_type=Question.AnswerType.BOOL,
+        answer_type=Question.AnswerType.NUMBER,
         image_port=Question.ImagePort.MAIN,
         required=False,
         direction=Question.Direction.VERTICAL,
@@ -553,7 +553,15 @@ def test_reader_study_copy(
         interface=ComponentInterfaceFactory(),
         overlay_segments={"foo": "bar"},
         look_up_table=lut,
+        widget=QuestionWidgetKindChoices.NUMBER_INPUT,
+        answer_max_value=20,
+        answer_min_value=1,
+        answer_step_size=0.5,
+        answer_min_length=1,
+        answer_max_length=3,
+        answer_match_pattern=r"^hello world$",
     )
+    CategoricalOptionFactory(question=question, title="option1")
     QuestionFactory(
         reader_study=rs,
         answer_type=Question.AnswerType.BOOL,
@@ -659,17 +667,27 @@ def test_reader_study_copy(
 
     assert question.reader_study == rs
     assert copied_question.pk != question.pk
-    assert copied_question.question_text == question.question_text
-    assert copied_question.help_text == question.help_text
-    assert copied_question.answer_type == question.answer_type
-    assert copied_question.image_port == question.image_port
-    assert copied_question.required == question.required
-    assert copied_question.direction == question.direction
-    assert copied_question.scoring_function == question.scoring_function
-    assert copied_question.order == question.order
-    assert copied_question.interface == question.interface
-    assert copied_question.look_up_table == question.look_up_table
-    assert copied_question.overlay_segments == question.overlay_segments
+    assert copied_question.reader_study != question.reader_study
+
+    # check that all but the specified fields have been copied over
+    non_copied_fields = [
+        "questionuserobjectpermission",
+        "questiongroupobjectpermission",
+        "answer",
+        "reader_study",
+        "id",
+        "created",
+        "modified",
+        "options",
+    ]
+    for field in copied_question._meta.get_fields():
+        if field.name not in non_copied_fields:
+            assert getattr(copied_question, field.name) == getattr(
+                question, field.name
+            )
+
+    assert copied_question.options.get().title == question.options.get().title
+    assert copied_question.options.get().pk != question.options.get().pk
 
     with django_capture_on_commit_callbacks(execute=True):
         response = get_view_for_user(