Skip to content

Commit 2d1457b

Browse files
Fixes: #13682 - Fix custom field exceptions and validation (#13685)
* 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 <[email protected]>
1 parent 9d85192 commit 2d1457b

File tree

2 files changed

+108
-15
lines changed

2 files changed

+108
-15
lines changed

netbox/extras/models/customfields.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ def clean(self):
282282
raise ValidationError({
283283
'default': _(
284284
'Invalid default value "{default}": {message}'
285-
).format(default=self.default, message=self.message)
285+
).format(default=self.default, message=err.message)
286286
})
287287

288288
# Minimum/maximum values can be set only for numeric fields
@@ -317,14 +317,6 @@ def clean(self):
317317
'choice_set': _("Choices may be set only on selection fields.")
318318
})
319319

320-
# A selection field's default (if any) must be present in its available choices
321-
if self.type == CustomFieldTypeChoices.TYPE_SELECT and self.default and self.default not in self.choices:
322-
raise ValidationError({
323-
'default': _(
324-
"The specified default value ({default}) is not listed as an available choice."
325-
).format(default=self.default)
326-
})
327-
328320
# Object fields must define an object_type; other fields must not
329321
if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT):
330322
if not self.object_type:
@@ -650,19 +642,22 @@ def validate(self, value):
650642

651643
# Validate selected choice
652644
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
653-
if value not in [c[0] for c in self.choices]:
645+
if value not in self.choice_set.values:
654646
raise ValidationError(
655-
_("Invalid choice ({value}). Available choices are: {choices}").format(
656-
value=value, choices=', '.join(self.choices)
647+
_("Invalid choice ({value}) for choice set {choiceset}.").format(
648+
value=value,
649+
choiceset=self.choice_set
657650
)
658651
)
659652

660653
# Validate all selected choices
661654
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
662-
if not set(value).issubset([c[0] for c in self.choices]):
655+
if not set(value).issubset(self.choice_set.values):
663656
raise ValidationError(
664-
_("Invalid choice(s) ({invalid_choices}). Available choices are: {available_choices}").format(
665-
invalid_choices=', '.join(value), available_choices=', '.join(self.choices))
657+
_("Invalid choice(s) ({value}) for choice set {choiceset}.").format(
658+
value=value,
659+
choiceset=self.choice_set
660+
)
666661
)
667662

668663
# Validate selected object
@@ -747,6 +742,13 @@ def choices(self):
747742
def choices_count(self):
748743
return len(self.choices)
749744

745+
@property
746+
def values(self):
747+
"""
748+
Returns an iterator of the valid choice values.
749+
"""
750+
return (x[0] for x in self.choices)
751+
750752
def clean(self):
751753
if not self.base_choices and not self.extra_choices:
752754
raise ValidationError(_("Must define base or extra choices."))

netbox/extras/tests/test_customfields.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,97 @@ def test_rename_customfield(self):
427427
self.assertNotIn('field1', site.custom_field_data)
428428
self.assertEqual(site.custom_field_data['field2'], FIELD_DATA)
429429

430+
def test_default_value_validation(self):
431+
choiceset = CustomFieldChoiceSet.objects.create(
432+
name="Test Choice Set",
433+
extra_choices=(
434+
('choice1', 'Choice 1'),
435+
('choice2', 'Choice 2'),
436+
)
437+
)
438+
site = Site.objects.create(name='Site 1', slug='site-1')
439+
object_type = ContentType.objects.get_for_model(Site)
440+
441+
# Text
442+
CustomField(name='test', type='text', required=True, default="Default text").full_clean()
443+
444+
# Integer
445+
CustomField(name='test', type='integer', required=True, default=1).full_clean()
446+
with self.assertRaises(ValidationError):
447+
CustomField(name='test', type='integer', required=True, default='xxx').full_clean()
448+
449+
# Boolean
450+
CustomField(name='test', type='boolean', required=True, default=True).full_clean()
451+
with self.assertRaises(ValidationError):
452+
CustomField(name='test', type='boolean', required=True, default='xxx').full_clean()
453+
454+
# Date
455+
CustomField(name='test', type='date', required=True, default="2023-02-25").full_clean()
456+
with self.assertRaises(ValidationError):
457+
CustomField(name='test', type='date', required=True, default='xxx').full_clean()
458+
459+
# Datetime
460+
CustomField(name='test', type='datetime', required=True, default="2023-02-25 02:02:02").full_clean()
461+
with self.assertRaises(ValidationError):
462+
CustomField(name='test', type='datetime', required=True, default='xxx').full_clean()
463+
464+
# URL
465+
CustomField(name='test', type='url', required=True, default="https://www.netbox.dev").full_clean()
466+
467+
# JSON
468+
CustomField(name='test', type='json', required=True, default='{"test": "object"}').full_clean()
469+
470+
# Selection
471+
CustomField(name='test', type='select', required=True, choice_set=choiceset, default='choice1').full_clean()
472+
with self.assertRaises(ValidationError):
473+
CustomField(name='test', type='select', required=True, choice_set=choiceset, default='xxx').full_clean()
474+
475+
# Multi-select
476+
CustomField(
477+
name='test',
478+
type='multiselect',
479+
required=True,
480+
choice_set=choiceset,
481+
default=['choice1'] # Single default choice
482+
).full_clean()
483+
CustomField(
484+
name='test',
485+
type='multiselect',
486+
required=True,
487+
choice_set=choiceset,
488+
default=['choice1', 'choice2'] # Multiple default choices
489+
).full_clean()
490+
with self.assertRaises(ValidationError):
491+
CustomField(
492+
name='test',
493+
type='multiselect',
494+
required=True,
495+
choice_set=choiceset,
496+
default=['xxx']
497+
).full_clean()
498+
499+
# Object
500+
CustomField(name='test', type='object', required=True, object_type=object_type, default=site.pk).full_clean()
501+
with self.assertRaises(ValidationError):
502+
CustomField(name='test', type='object', required=True, object_type=object_type, default="xxx").full_clean()
503+
504+
# Multi-object
505+
CustomField(
506+
name='test',
507+
type='multiobject',
508+
required=True,
509+
object_type=object_type,
510+
default=[site.pk]
511+
).full_clean()
512+
with self.assertRaises(ValidationError):
513+
CustomField(
514+
name='test',
515+
type='multiobject',
516+
required=True,
517+
object_type=object_type,
518+
default=["xxx"]
519+
).full_clean()
520+
430521

431522
class CustomFieldManagerTest(TestCase):
432523

0 commit comments

Comments
 (0)