mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
10348 add decimal custom field (#10422)
* 10348 add decimal custom field * 10348 fix tests * 10348 add documentation * Rearrange custom fields to be ordered consistently * Rename number_field to integer_field for clarity * Clean up validation logic * Apply suggested changes from PR * Store decimal custom field values natively * Fix filter test * Update custom field model migrations to use new encoder Co-authored-by: jeremystretch <jstretch@ns1.com>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import re
|
||||
from datetime import datetime, date
|
||||
import decimal
|
||||
|
||||
import django_filters
|
||||
from django import forms
|
||||
@ -219,14 +220,11 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
|
||||
})
|
||||
|
||||
# Minimum/maximum values can be set only for numeric fields
|
||||
if self.validation_minimum is not None and self.type != CustomFieldTypeChoices.TYPE_INTEGER:
|
||||
raise ValidationError({
|
||||
'validation_minimum': "A minimum value may be set only for numeric fields"
|
||||
})
|
||||
if self.validation_maximum is not None and self.type != CustomFieldTypeChoices.TYPE_INTEGER:
|
||||
raise ValidationError({
|
||||
'validation_maximum': "A maximum value may be set only for numeric fields"
|
||||
})
|
||||
if self.type not in (CustomFieldTypeChoices.TYPE_INTEGER, CustomFieldTypeChoices.TYPE_DECIMAL):
|
||||
if self.validation_minimum:
|
||||
raise ValidationError({'validation_minimum': "A minimum value may be set only for numeric fields"})
|
||||
if self.validation_maximum:
|
||||
raise ValidationError({'validation_maximum': "A maximum value may be set only for numeric fields"})
|
||||
|
||||
# Regex validation can be set only for text fields
|
||||
regex_types = (
|
||||
@ -317,6 +315,17 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
|
||||
max_value=self.validation_maximum
|
||||
)
|
||||
|
||||
# Decimal
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_DECIMAL:
|
||||
field = forms.DecimalField(
|
||||
required=required,
|
||||
initial=initial,
|
||||
max_digits=12,
|
||||
decimal_places=4,
|
||||
min_value=self.validation_minimum,
|
||||
max_value=self.validation_maximum
|
||||
)
|
||||
|
||||
# Boolean
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||
choices = (
|
||||
@ -426,6 +435,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||
filter_class = filters.MultiValueNumberFilter
|
||||
|
||||
# Decimal
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_DECIMAL:
|
||||
filter_class = filters.MultiValueDecimalFilter
|
||||
|
||||
# Boolean
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||
filter_class = django_filters.BooleanFilter
|
||||
@ -475,7 +488,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
|
||||
raise ValidationError(f"Value must match regex '{self.validation_regex}'")
|
||||
|
||||
# Validate integer
|
||||
if self.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||
if type(value) is not int:
|
||||
raise ValidationError("Value must be an integer.")
|
||||
if self.validation_minimum is not None and value < self.validation_minimum:
|
||||
@ -483,12 +496,23 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
|
||||
if self.validation_maximum is not None and value > self.validation_maximum:
|
||||
raise ValidationError(f"Value must not exceed {self.validation_maximum}")
|
||||
|
||||
# Validate decimal
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_DECIMAL:
|
||||
try:
|
||||
decimal.Decimal(value)
|
||||
except decimal.InvalidOperation:
|
||||
raise ValidationError("Value must be a decimal.")
|
||||
if self.validation_minimum is not None and value < self.validation_minimum:
|
||||
raise ValidationError(f"Value must be at least {self.validation_minimum}")
|
||||
if self.validation_maximum is not None and value > self.validation_maximum:
|
||||
raise ValidationError(f"Value must not exceed {self.validation_maximum}")
|
||||
|
||||
# Validate boolean
|
||||
if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value not in [True, False, 1, 0]:
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value not in [True, False, 1, 0]:
|
||||
raise ValidationError("Value must be true or false.")
|
||||
|
||||
# Validate date
|
||||
if self.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||
if type(value) is not date:
|
||||
try:
|
||||
datetime.strptime(value, '%Y-%m-%d')
|
||||
@ -496,14 +520,14 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
|
||||
raise ValidationError("Date values must be in the format YYYY-MM-DD.")
|
||||
|
||||
# Validate selected choice
|
||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
if value not in self.choices:
|
||||
raise ValidationError(
|
||||
f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}"
|
||||
)
|
||||
|
||||
# Validate all selected choices
|
||||
if self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||
if not set(value).issubset(self.choices):
|
||||
raise ValidationError(
|
||||
f"Invalid choice(s) ({', '.join(value)}). Available choices are: {', '.join(self.choices)}"
|
||||
|
Reference in New Issue
Block a user