Repository: feder-observatory/stellarphot

Issue:
Making a new item confuses the widget
If you have at least one item, e.g. camera, and you select the "Make new..." option and then select the original item again then the widget display is blank. The reason is that the private method `_init_ui` from ipyautoui resets the values of each individual text box (or whatever) but does not change the value of the widget as a whole. 

As a result, after selecting "Make new..." with widget value is still the value for the existing item. So when this custom widget tries to redisplay after you select the existing item again, the UI isn't updated because the UI thinks nothing has changed, so no handlers are triggered.

This description is probably a little confusing, but trust me it is an issue. Fix incoming momentarily.

Discussion:

Patch:
diff --git a/stellarphot/settings/custom_widgets.py b/stellarphot/settings/custom_widgets.py
index 160192e..1a26f59 100644
--- a/stellarphot/settings/custom_widgets.py
+++ b/stellarphot/settings/custom_widgets.py
@@ -72,7 +72,7 @@ class ChooseOrMakeNew(ipw.VBox):
             message=f"Replace value of this {self._display_name}?",
         )
 
-        self._item_widget = self._make_new_widget()
+        self._item_widget, self._widget_value_new_item = self._make_new_widget()
 
         self._edit_button.on_click(self._edit_button_action)
 
@@ -156,6 +156,14 @@ class ChooseOrMakeNew(ipw.VBox):
             # This sets the ui back to its original state when created, i.e.
             # everything is empty.
             self._item_widget._init_ui()
+
+            # Fun fact: _init_ui does not reset the value of the widget. Also,
+            # setting the value fails if you try to set it to an empty dict because that
+            # is not a valid value for the pydantic model for the widget.
+            # So we have to set each of the values individually.
+            for key, value in self._widget_value_new_item.items():
+                self._item_widget.value[key] = value
+
             self._item_widget.show_savebuttonbar = True
             self._item_widget.disabled = False
 
@@ -253,6 +261,8 @@ class ChooseOrMakeNew(ipw.VBox):
     def _make_new_widget(self):
         """
         Make a new widget for the item type and set up actions for the save button.
+
+        Also returns the initial value of the widget for resetting the widget value.
         """
         match self._item_type_name:
             case "camera" | Camera.__name__:
@@ -290,7 +300,7 @@ class ChooseOrMakeNew(ipw.VBox):
         # This is the mechanism for adding callbacks to the save button.
         new_widget.savebuttonbar.fns_onsave_add_action(saver)
         new_widget.savebuttonbar.fns_onsave_add_action(update_choices_and_select_new)
-        return new_widget
+        return new_widget, new_widget.value.copy()
 
     def _handle_confirmation(self):
         """


Test patch:
diff --git a/stellarphot/settings/tests/test_custom_widgets.py b/stellarphot/settings/tests/test_custom_widgets.py
index f37c1c5..153fd3b 100644
--- a/stellarphot/settings/tests/test_custom_widgets.py
+++ b/stellarphot/settings/tests/test_custom_widgets.py
@@ -71,6 +71,34 @@ class TestChooseOrMakeNew:
         assert len(cameras.as_dict) == 1
         assert list(cameras.as_dict.values())[0].model_dump() == TEST_CAMERA_VALUES
 
+    @pytest.mark.parametrize(
+        "item_type,setting",
+        [
+            ("camera", TEST_CAMERA_VALUES),
+            ("observatory", DEFAULT_OBSERVATORY_SETTINGS),
+            ("passband_map", DEFAULT_PASSBAND_MAP),
+        ],
+    )
+    def test_make_new_with_existing_item_resets_value(
+        self, tmp_path, item_type, setting
+    ):
+        # When "make a new" item is selected the value of the widget should be
+        # the same as when a widget of that type is created.
+
+        # Make a camera widget just to get the value for a new item.
+        choose_or_make_new = ChooseOrMakeNew(item_type, _testing_path=tmp_path)
+        value_when_new = choose_or_make_new._item_widget.value.copy()
+
+        # Make a camera
+        saved = SavedSettings(_testing_path=tmp_path)
+        item = choose_or_make_new._item_widget.model(**setting)
+        saved.add_item(item)
+
+        # Make a camera widget and select "Make new"
+        choose_or_make_new = ChooseOrMakeNew(item_type, _testing_path=tmp_path)
+        choose_or_make_new._choose_existing.value = "none"
+        assert choose_or_make_new._item_widget.value == value_when_new
+
     def test_edit_requires_confirmation(self, tmp_path):
         # Should require confirmation if the item already exists
         saved = SavedSettings(_testing_path=tmp_path)