Skip to content

Commit

Permalink
Closes #10729: Add date & time custom field type (#11857)
Browse files Browse the repository at this point in the history
* Add datetime custom field type

* Update custom field tests
  • Loading branch information
jeremystretch authored Feb 28, 2023
1 parent 7994073 commit 5517963
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 31 deletions.
1 change: 1 addition & 0 deletions docs/customization/custom-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Custom fields may be created by navigating to Customization > Custom Fields. Net
* Decimal: A fixed-precision decimal number (4 decimal places)
* Boolean: True or false
* Date: A date in ISO 8601 format (YYYY-MM-DD)
* Date & time: A date and time in ISO 8601 format (YYYY-MM-DD HH:MM:SS)
* URL: This will be presented as a link in the web UI
* JSON: Arbitrary data stored in JSON format
* Selection: A selection of one of several pre-defined custom choices
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/version-3.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ A new ASN range model has been introduced to facilitate the provisioning of new

* [#9073](https://github.com/netbox-community/netbox/issues/9073) - Enable syncing config context data from remote sources
* [#9653](https://github.com/netbox-community/netbox/issues/9653) - Enable setting a default platform for device types
* [#10729](https://github.com/netbox-community/netbox/issues/10729) - Add date & time custom field type
* [#11254](https://github.com/netbox-community/netbox/issues/11254) - Introduce the `X-Request-ID` HTTP header to annotate the unique ID of each request for change logging
* [#11440](https://github.com/netbox-community/netbox/issues/11440) - Add an `enabled` field for device type interfaces
* [#11517](https://github.com/netbox-community/netbox/issues/11517) - Standardize the inclusion of related objects across the entire UI
Expand Down
2 changes: 2 additions & 0 deletions netbox/extras/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class CustomFieldTypeChoices(ChoiceSet):
TYPE_DECIMAL = 'decimal'
TYPE_BOOLEAN = 'boolean'
TYPE_DATE = 'date'
TYPE_DATETIME = 'datetime'
TYPE_URL = 'url'
TYPE_JSON = 'json'
TYPE_SELECT = 'select'
Expand All @@ -27,6 +28,7 @@ class CustomFieldTypeChoices(ChoiceSet):
(TYPE_DECIMAL, 'Decimal'),
(TYPE_BOOLEAN, 'Boolean (true/false)'),
(TYPE_DATE, 'Date'),
(TYPE_DATETIME, 'Date & time'),
(TYPE_URL, 'URL'),
(TYPE_JSON, 'JSON'),
(TYPE_SELECT, 'Selection'),
Expand Down
32 changes: 27 additions & 5 deletions netbox/extras/models/customfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
DynamicModelMultipleChoiceField, JSONField, LaxURLField,
)
from utilities.forms.utils import add_blank_choice
from utilities.forms.widgets import DatePicker
from utilities.forms.widgets import DatePicker, DateTimePicker
from utilities.querysets import RestrictedQuerySet
from utilities.validators import validate_regex

Expand Down Expand Up @@ -306,8 +306,9 @@ def serialize(self, value):
"""
if value is None:
return value
if self.type == CustomFieldTypeChoices.TYPE_DATE and type(value) is date:
return value.isoformat()
if self.type in (CustomFieldTypeChoices.TYPE_DATE, CustomFieldTypeChoices.TYPE_DATETIME):
if type(value) in (date, datetime):
return value.isoformat()
if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
return value.pk
if self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
Expand All @@ -325,6 +326,11 @@ def deserialize(self, value):
return date.fromisoformat(value)
except ValueError:
return value
if self.type == CustomFieldTypeChoices.TYPE_DATETIME:
try:
return datetime.fromisoformat(value)
except ValueError:
return value
if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
model = self.object_type.model_class()
return model.objects.filter(pk=value).first()
Expand Down Expand Up @@ -380,6 +386,10 @@ def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibil
elif self.type == CustomFieldTypeChoices.TYPE_DATE:
field = forms.DateField(required=required, initial=initial, widget=DatePicker())

# Date & time
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
field = forms.DateTimeField(required=required, initial=initial, widget=DateTimePicker())

# Select
elif self.type in (CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT):
choices = [(c, c) for c in self.choices]
Expand Down Expand Up @@ -490,6 +500,10 @@ def to_filter(self, lookup_expr=None):
elif self.type == CustomFieldTypeChoices.TYPE_DATE:
filter_class = filters.MultiValueDateFilter

# Date & time
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
filter_class = filters.MultiValueDateTimeFilter

# Select
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
filter_class = filters.MultiValueCharFilter
Expand Down Expand Up @@ -558,9 +572,17 @@ def validate(self, value):
elif self.type == CustomFieldTypeChoices.TYPE_DATE:
if type(value) is not date:
try:
datetime.strptime(value, '%Y-%m-%d')
date.fromisoformat(value)
except ValueError:
raise ValidationError("Date values must be in ISO 8601 format (YYYY-MM-DD).")

# Validate date & time
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
if type(value) is not datetime:
try:
datetime.fromisoformat(value)
except ValueError:
raise ValidationError("Date values must be in the format YYYY-MM-DD.")
raise ValidationError("Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS).")

# Validate selected choice
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
Expand Down
Loading

0 comments on commit 5517963

Please sign in to comment.