mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 12:12:53 -06:00
* Fixes: #13682 - Fix custom field exceptions and validation * Add tests * Remove default setting for multi-select/multi-object and return slice of choices and annotate. * Remove redundant default choice valiadtion; introduce values property on CustomFieldChoiceSet * Refactor test --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
parent
9d851924c8
commit
2d1457b94b
@ -282,7 +282,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
raise ValidationError({
|
||||
'default': _(
|
||||
'Invalid default value "{default}": {message}'
|
||||
).format(default=self.default, message=self.message)
|
||||
).format(default=self.default, message=err.message)
|
||||
})
|
||||
|
||||
# Minimum/maximum values can be set only for numeric fields
|
||||
@ -317,14 +317,6 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
'choice_set': _("Choices may be set only on selection fields.")
|
||||
})
|
||||
|
||||
# A selection field's default (if any) must be present in its available choices
|
||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT and self.default and self.default not in self.choices:
|
||||
raise ValidationError({
|
||||
'default': _(
|
||||
"The specified default value ({default}) is not listed as an available choice."
|
||||
).format(default=self.default)
|
||||
})
|
||||
|
||||
# Object fields must define an object_type; other fields must not
|
||||
if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT):
|
||||
if not self.object_type:
|
||||
@ -650,19 +642,22 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
|
||||
# Validate selected choice
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
if value not in [c[0] for c in self.choices]:
|
||||
if value not in self.choice_set.values:
|
||||
raise ValidationError(
|
||||
_("Invalid choice ({value}). Available choices are: {choices}").format(
|
||||
value=value, choices=', '.join(self.choices)
|
||||
_("Invalid choice ({value}) for choice set {choiceset}.").format(
|
||||
value=value,
|
||||
choiceset=self.choice_set
|
||||
)
|
||||
)
|
||||
|
||||
# Validate all selected choices
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||
if not set(value).issubset([c[0] for c in self.choices]):
|
||||
if not set(value).issubset(self.choice_set.values):
|
||||
raise ValidationError(
|
||||
_("Invalid choice(s) ({invalid_choices}). Available choices are: {available_choices}").format(
|
||||
invalid_choices=', '.join(value), available_choices=', '.join(self.choices))
|
||||
_("Invalid choice(s) ({value}) for choice set {choiceset}.").format(
|
||||
value=value,
|
||||
choiceset=self.choice_set
|
||||
)
|
||||
)
|
||||
|
||||
# Validate selected object
|
||||
@ -747,6 +742,13 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
||||
def choices_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""
|
||||
Returns an iterator of the valid choice values.
|
||||
"""
|
||||
return (x[0] for x in self.choices)
|
||||
|
||||
def clean(self):
|
||||
if not self.base_choices and not self.extra_choices:
|
||||
raise ValidationError(_("Must define base or extra choices."))
|
||||
|
@ -427,6 +427,97 @@ class CustomFieldTest(TestCase):
|
||||
self.assertNotIn('field1', site.custom_field_data)
|
||||
self.assertEqual(site.custom_field_data['field2'], FIELD_DATA)
|
||||
|
||||
def test_default_value_validation(self):
|
||||
choiceset = CustomFieldChoiceSet.objects.create(
|
||||
name="Test Choice Set",
|
||||
extra_choices=(
|
||||
('choice1', 'Choice 1'),
|
||||
('choice2', 'Choice 2'),
|
||||
)
|
||||
)
|
||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||
object_type = ContentType.objects.get_for_model(Site)
|
||||
|
||||
# Text
|
||||
CustomField(name='test', type='text', required=True, default="Default text").full_clean()
|
||||
|
||||
# Integer
|
||||
CustomField(name='test', type='integer', required=True, default=1).full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(name='test', type='integer', required=True, default='xxx').full_clean()
|
||||
|
||||
# Boolean
|
||||
CustomField(name='test', type='boolean', required=True, default=True).full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(name='test', type='boolean', required=True, default='xxx').full_clean()
|
||||
|
||||
# Date
|
||||
CustomField(name='test', type='date', required=True, default="2023-02-25").full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(name='test', type='date', required=True, default='xxx').full_clean()
|
||||
|
||||
# Datetime
|
||||
CustomField(name='test', type='datetime', required=True, default="2023-02-25 02:02:02").full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(name='test', type='datetime', required=True, default='xxx').full_clean()
|
||||
|
||||
# URL
|
||||
CustomField(name='test', type='url', required=True, default="https://www.netbox.dev").full_clean()
|
||||
|
||||
# JSON
|
||||
CustomField(name='test', type='json', required=True, default='{"test": "object"}').full_clean()
|
||||
|
||||
# Selection
|
||||
CustomField(name='test', type='select', required=True, choice_set=choiceset, default='choice1').full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(name='test', type='select', required=True, choice_set=choiceset, default='xxx').full_clean()
|
||||
|
||||
# Multi-select
|
||||
CustomField(
|
||||
name='test',
|
||||
type='multiselect',
|
||||
required=True,
|
||||
choice_set=choiceset,
|
||||
default=['choice1'] # Single default choice
|
||||
).full_clean()
|
||||
CustomField(
|
||||
name='test',
|
||||
type='multiselect',
|
||||
required=True,
|
||||
choice_set=choiceset,
|
||||
default=['choice1', 'choice2'] # Multiple default choices
|
||||
).full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(
|
||||
name='test',
|
||||
type='multiselect',
|
||||
required=True,
|
||||
choice_set=choiceset,
|
||||
default=['xxx']
|
||||
).full_clean()
|
||||
|
||||
# Object
|
||||
CustomField(name='test', type='object', required=True, object_type=object_type, default=site.pk).full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(name='test', type='object', required=True, object_type=object_type, default="xxx").full_clean()
|
||||
|
||||
# Multi-object
|
||||
CustomField(
|
||||
name='test',
|
||||
type='multiobject',
|
||||
required=True,
|
||||
object_type=object_type,
|
||||
default=[site.pk]
|
||||
).full_clean()
|
||||
with self.assertRaises(ValidationError):
|
||||
CustomField(
|
||||
name='test',
|
||||
type='multiobject',
|
||||
required=True,
|
||||
object_type=object_type,
|
||||
default=["xxx"]
|
||||
).full_clean()
|
||||
|
||||
|
||||
class CustomFieldManagerTest(TestCase):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user