Add datetime custom field type

This commit is contained in:
jeremystretch 2023-02-28 12:33:58 -05:00
parent 7994073687
commit 16daeabccf
4 changed files with 32 additions and 5 deletions

View File

@ -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) * Decimal: A fixed-precision decimal number (4 decimal places)
* Boolean: True or false * Boolean: True or false
* Date: A date in ISO 8601 format (YYYY-MM-DD) * 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 * URL: This will be presented as a link in the web UI
* JSON: Arbitrary data stored in JSON format * JSON: Arbitrary data stored in JSON format
* Selection: A selection of one of several pre-defined custom choices * Selection: A selection of one of several pre-defined custom choices

View File

@ -13,6 +13,7 @@ class CustomFieldTypeChoices(ChoiceSet):
TYPE_DECIMAL = 'decimal' TYPE_DECIMAL = 'decimal'
TYPE_BOOLEAN = 'boolean' TYPE_BOOLEAN = 'boolean'
TYPE_DATE = 'date' TYPE_DATE = 'date'
TYPE_DATETIME = 'datetime'
TYPE_URL = 'url' TYPE_URL = 'url'
TYPE_JSON = 'json' TYPE_JSON = 'json'
TYPE_SELECT = 'select' TYPE_SELECT = 'select'
@ -27,6 +28,7 @@ class CustomFieldTypeChoices(ChoiceSet):
(TYPE_DECIMAL, 'Decimal'), (TYPE_DECIMAL, 'Decimal'),
(TYPE_BOOLEAN, 'Boolean (true/false)'), (TYPE_BOOLEAN, 'Boolean (true/false)'),
(TYPE_DATE, 'Date'), (TYPE_DATE, 'Date'),
(TYPE_DATETIME, 'Date & time'),
(TYPE_URL, 'URL'), (TYPE_URL, 'URL'),
(TYPE_JSON, 'JSON'), (TYPE_JSON, 'JSON'),
(TYPE_SELECT, 'Selection'), (TYPE_SELECT, 'Selection'),

View File

@ -25,7 +25,7 @@ from utilities.forms.fields import (
DynamicModelMultipleChoiceField, JSONField, LaxURLField, DynamicModelMultipleChoiceField, JSONField, LaxURLField,
) )
from utilities.forms.utils import add_blank_choice 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.querysets import RestrictedQuerySet
from utilities.validators import validate_regex from utilities.validators import validate_regex
@ -306,7 +306,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
""" """
if value is None: if value is None:
return value return value
if self.type == CustomFieldTypeChoices.TYPE_DATE and type(value) is date: if self.type in (CustomFieldTypeChoices.TYPE_DATE, CustomFieldTypeChoices.TYPE_DATETIME):
if type(value) in (date, datetime):
return value.isoformat() return value.isoformat()
if self.type == CustomFieldTypeChoices.TYPE_OBJECT: if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
return value.pk return value.pk
@ -325,6 +326,11 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
return date.fromisoformat(value) return date.fromisoformat(value)
except ValueError: except ValueError:
return value return value
if self.type == CustomFieldTypeChoices.TYPE_DATETIME:
try:
return datetime.fromisoformat(value)
except ValueError:
return value
if self.type == CustomFieldTypeChoices.TYPE_OBJECT: if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
model = self.object_type.model_class() model = self.object_type.model_class()
return model.objects.filter(pk=value).first() return model.objects.filter(pk=value).first()
@ -380,6 +386,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
elif self.type == CustomFieldTypeChoices.TYPE_DATE: elif self.type == CustomFieldTypeChoices.TYPE_DATE:
field = forms.DateField(required=required, initial=initial, widget=DatePicker()) 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 # Select
elif self.type in (CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT): elif self.type in (CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT):
choices = [(c, c) for c in self.choices] choices = [(c, c) for c in self.choices]
@ -490,6 +500,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
elif self.type == CustomFieldTypeChoices.TYPE_DATE: elif self.type == CustomFieldTypeChoices.TYPE_DATE:
filter_class = filters.MultiValueDateFilter filter_class = filters.MultiValueDateFilter
# Date & time
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
filter_class = filters.MultiValueDateTimeFilter
# Select # Select
elif self.type == CustomFieldTypeChoices.TYPE_SELECT: elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
filter_class = filters.MultiValueCharFilter filter_class = filters.MultiValueCharFilter
@ -558,9 +572,17 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
elif self.type == CustomFieldTypeChoices.TYPE_DATE: elif self.type == CustomFieldTypeChoices.TYPE_DATE:
if type(value) is not date: if type(value) is not date:
try: try:
datetime.strptime(value, '%Y-%m-%d') date.fromisoformat(value)
except ValueError: except ValueError:
raise ValidationError("Date values must be in the format YYYY-MM-DD.") 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 and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS).")
# Validate selected choice # Validate selected choice
elif self.type == CustomFieldTypeChoices.TYPE_SELECT: elif self.type == CustomFieldTypeChoices.TYPE_SELECT:

View File

@ -9,6 +9,8 @@
{% checkmark value false="False" %} {% checkmark value false="False" %}
{% elif customfield.type == 'date' and value %} {% elif customfield.type == 'date' and value %}
{{ value|annotated_date }} {{ value|annotated_date }}
{% elif customfield.type == 'datetime' and value %}
{{ value|annotated_date }}
{% elif customfield.type == 'url' and value %} {% elif customfield.type == 'url' and value %}
<a href="{{ value }}">{{ value|truncatechars:70 }}</a> <a href="{{ value }}">{{ value|truncatechars:70 }}</a>
{% elif customfield.type == 'json' and value %} {% elif customfield.type == 'json' and value %}