1
0
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:
Arthur Hanson
2022-09-30 13:03:24 -07:00
committed by GitHub
parent ada5c58acf
commit af8bb0c4b9
26 changed files with 343 additions and 207 deletions

View File

@ -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)}"