Repository: netbox-community/netbox

Issue:
Add Date(Time)Var for scripts to allow much easier date input
### NetBox version

v3.7.6

### Feature type

New functionality

### Proposed functionality

Add a new `DateVar` that allows people that want to run a script to provide a date with a datepicker, not with e.g. just a simple text input field.

![image](https://github.com/netbox-community/netbox/assets/63594136/4a225eb4-5ff0-4dbf-bd1a-74fa45bdb001)

***I will provide patches, if this issue is accepted => You can assign me to this issue***

### Use case

For users of scripts: No need to check out the date format? Is it YYYY-MM-DD or YYYY-DD-MM etc. It makes selecting input easier. Furthermore if you submit an invalid date (E.g. 2024-02-31) this would be already rejected during the form submission

For developers of scripts: No need to manually parse dates => Less code, less error handling in case of invalid input.

### Database changes

None

### External dependencies

None


Discussion:

Resolving patch:
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -24,6 +24,7 @@
 from utilities.exceptions import AbortScript, AbortTransaction
 from utilities.forms import add_blank_choice
 from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
+from utilities.forms.widgets import DatePicker, DateTimePicker
 from .context_managers import event_tracking
 from .forms import ScriptForm
 
@@ -31,6 +32,8 @@
     'BaseScript',
     'BooleanVar',
     'ChoiceVar',
+    'DateVar',
+    'DateTimeVar',
     'FileVar',
     'IntegerVar',
     'IPAddressVar',
@@ -172,6 +175,28 @@ def __init__(self, choices, *args, **kwargs):
         self.field_attrs['choices'] = add_blank_choice(choices)
 
 
+class DateVar(ScriptVariable):
+    """
+    A date.
+    """
+    form_field = forms.DateField
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.form_field.widget = DatePicker()
+
+
+class DateTimeVar(ScriptVariable):
+    """
+    A date and a time.
+    """
+    form_field = forms.DateTimeField
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.form_field.widget = DateTimePicker()
+
+
 class MultiChoiceVar(ScriptVariable):
     """
     Like ChoiceVar, but allows for the selection of multiple choices.


Test patch:
diff --git a/netbox/extras/tests/test_scripts.py b/netbox/extras/tests/test_scripts.py
--- a/netbox/extras/tests/test_scripts.py
+++ b/netbox/extras/tests/test_scripts.py
@@ -1,4 +1,5 @@
 import tempfile
+from datetime import date, datetime, timezone
 
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import TestCase
@@ -322,3 +323,47 @@ class TestScript(Script):
         form = TestScript().as_form(data, None)
         self.assertTrue(form.is_valid())
         self.assertEqual(form.cleaned_data['var1'], IPNetwork(data['var1']))
+
+    def test_datevar(self):
+
+        class TestScript(Script):
+
+            var1 = DateVar()
+            var2 = DateVar(required=False)
+
+        # Test date validation
+        data = {'var1': 'not a date'}
+        form = TestScript().as_form(data, None)
+        self.assertFalse(form.is_valid())
+        self.assertIn('var1', form.errors)
+
+        # Validate valid data
+        input_date = date(2024, 4, 1)
+        data = {'var1': input_date}
+        form = TestScript().as_form(data, None)
+        self.assertTrue(form.is_valid())
+        self.assertEqual(form.cleaned_data['var1'], input_date)
+        # Validate required=False works for this Var type
+        self.assertEqual(form.cleaned_data['var2'], None)
+
+    def test_datetimevar(self):
+
+        class TestScript(Script):
+
+            var1 = DateTimeVar()
+            var2 = DateTimeVar(required=False)
+
+        # Test datetime validation
+        data = {'var1': 'not a datetime'}
+        form = TestScript().as_form(data, None)
+        self.assertFalse(form.is_valid())
+        self.assertIn('var1', form.errors)
+
+        # Validate valid data
+        input_datetime = datetime(2024, 4, 1, 8, 0, 0, 0, timezone.utc)
+        data = {'var1': input_datetime}
+        form = TestScript().as_form(data, None)
+        self.assertTrue(form.is_valid())
+        self.assertEqual(form.cleaned_data['var1'], input_datetime)
+        # Validate required=False works for this Var type
+        self.assertEqual(form.cleaned_data['var2'], None)