Repository: 12rambau/sepal_ui

Issue:
Can't instantiate a sw.DatePicker with initial v_model
Is not possible to instantiate the sepal DatePicker with an initially given date through the `v_model` parameter

Discussion:

Patch:
diff --git a/docs/source/modules/sepal_ui.sepalwidgets.DatePicker.rst b/docs/source/modules/sepal_ui.sepalwidgets.DatePicker.rst
index 867227cb..322cca23 100644
--- a/docs/source/modules/sepal_ui.sepalwidgets.DatePicker.rst
+++ b/docs/source/modules/sepal_ui.sepalwidgets.DatePicker.rst
@@ -9,6 +9,7 @@ sepal\_ui.sepalwidgets.DatePicker
     
         ~DatePicker.menu
         ~DatePicker.disabled
+        ~DatePicker.date_text
         
     .. rubric:: Methods
     
@@ -17,7 +18,13 @@ sepal\_ui.sepalwidgets.DatePicker
         
         ~Datepicker.close_menu
         ~DatePicker.disable
+        ~DatePicker.is_valid_date
+        ~DatePicker.check_date
         
 .. automethod:: sepal_ui.sepalwidgets.DatePicker.close_menu
 
-.. automethod:: sepal_ui.sepalwidgets.DatePicker.disable
\ No newline at end of file
+.. automethod:: sepal_ui.sepalwidgets.DatePicker.disable
+
+.. automethod:: sepal_ui.sepalwidgets.DatePicker.check_date
+
+.. autofunction:: sepal_ui.sepalwidgets.DatePicker.disable
\ No newline at end of file
diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py
index 7d003229..bf1adf0b 100644
--- a/sepal_ui/sepalwidgets/inputs.py
+++ b/sepal_ui/sepalwidgets/inputs.py
@@ -1,4 +1,5 @@
 from pathlib import Path
+from datetime import datetime
 
 import ipyvuetify as v
 from traitlets import link, Int, Any, List, observe, Dict, Unicode, Bool
@@ -40,6 +41,9 @@ class DatePicker(v.Layout, SepalWidget):
     menu = None
     "v.Menu: the menu widget to display the datepicker"
 
+    date_text = None
+    "v.TextField: the text field of the datepicker widget"
+
     disabled = Bool(False).tag(sync=True)
     "traitlets.Bool: the disabled status of the Datepicker object"
 
@@ -48,7 +52,7 @@ class DatePicker(v.Layout, SepalWidget):
         # create the widgets
         date_picker = v.DatePicker(no_title=True, v_model=None, scrollable=True)
 
-        date_text = v.TextField(
+        self.date_text = v.TextField(
             v_model=None,
             label=label,
             hint="YYYY-MM-DD format",
@@ -69,7 +73,7 @@ class DatePicker(v.Layout, SepalWidget):
                 {
                     "name": "activator",
                     "variable": "menuData",
-                    "children": date_text,
+                    "children": self.date_text,
                 }
             ],
         )
@@ -84,8 +88,28 @@ class DatePicker(v.Layout, SepalWidget):
         # call the constructor
         super().__init__(**kwargs)
 
-        jslink((date_picker, "v_model"), (date_text, "v_model"))
-        jslink((date_picker, "v_model"), (self, "v_model"))
+        jslink((date_picker, "v_model"), (self.date_text, "v_model"))
+        jslink((self, "v_model"), (date_picker, "v_model"))
+
+    @observe("v_model")
+    def check_date(self, change):
+        """
+        A method to check if the value of the set v_model is a correctly formated date
+        Reset the widget and display an error if it's not the case
+        """
+
+        self.date_text.error_messages = None
+
+        # exit immediately if nothing is set
+        if change["new"] is None:
+            return
+
+        # change the error status
+        if not self.is_valid_date(change["new"]):
+            msg = self.date_text.hint
+            self.date_text.error_messages = msg
+
+        return
 
     @observe("v_model")
     def close_menu(self, change):
@@ -104,6 +128,27 @@ class DatePicker(v.Layout, SepalWidget):
 
         return
 
+    @staticmethod
+    def is_valid_date(date):
+        """
+        Check if the date is provided using the date format required for the widget
+
+        Args:
+            date (str): the date to test in YYYY-MM-DD format
+
+        Return:
+            (bool): the date to test
+        """
+
+        try:
+            date = datetime.strptime(date, "%Y-%m-%d")
+            valid = True
+
+        except Exception:
+            valid = False
+
+        return valid
+
 
 class FileInput(v.Flex, SepalWidget):
     """


Test patch:
diff --git a/tests/test_DatePicker.py b/tests/test_DatePicker.py
index e5f5d06f..48993736 100644
--- a/tests/test_DatePicker.py
+++ b/tests/test_DatePicker.py
@@ -17,6 +17,11 @@ class TestDatePicker:
         datepicker = sw.DatePicker("toto")
         assert isinstance(datepicker, sw.DatePicker)
 
+        # datepicker with default value
+        value = "2022-03-14"
+        datepicker = sw.DatePicker(v_model=value)
+        assert datepicker.v_model == value
+
         return
 
     def test_bind(self, datepicker):
@@ -43,6 +48,34 @@ class TestDatePicker:
 
         return
 
+    def test_is_valid_date(self, datepicker):
+
+        # a nicely shaped date
+        test = "2022-03-14"
+        assert datepicker.is_valid_date(test) is True
+
+        # a badly shaped date
+        test = "2022-50-14"
+        assert datepicker.is_valid_date(test) is False
+
+        return
+
+    def test_check_date(self, datepicker):
+
+        # manually update the value with a badely shaped date
+        test = "2022-50-14"
+        datepicker.v_model = test
+        assert datepicker.v_model == test
+        assert datepicker.date_text.error_messages is not None
+
+        # manually update the value with a nicely shaped date
+        test = "2022-03-14"
+        datepicker.v_model = test
+        assert datepicker.v_model == test
+        assert datepicker.date_text.error_messages is None
+
+        return
+
     @pytest.fixture
     def datepicker(self):
         """create a default datepicker"""