mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* Initial work on custom field choice sets * Rename choices to extra_choices (prep for #12194) * Remove CustomField.choices * Add & update tests * Clean up table columns * Add order_alphanetically boolean for choice sets * Introduce ArrayColumn for choice lists * Show dependent custom fields on choice set view * Update custom fields documentation * Introduce ArrayWidget for more convenient editing of choices * Incorporate PR feedback * Misc cleanup * Initial work on predefined choices for custom fields * Misc cleanup * Add IATA airport codes * #13241: Add support for custom field choice labels * Restore ArrayColumn * Misc cleanup * Change extra_choices back to a nested ArrayField to preserve choice ordering * Hack to bypass GraphQL API test utility absent support for nested ArrayFields
This commit is contained in:
@ -15,17 +15,18 @@ from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from extras.choices import *
|
||||
from extras.data import CHOICE_SETS
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from netbox.models.features import CloningMixin, ExportTemplatesMixin
|
||||
from netbox.search import FieldTypes
|
||||
from utilities import filters
|
||||
from utilities.forms.fields import (
|
||||
CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVMultipleChoiceField, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, JSONField, LaxURLField,
|
||||
CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVMultipleChoiceField, DynamicChoiceField,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, DynamicMultipleChoiceField, JSONField, LaxURLField,
|
||||
)
|
||||
from utilities.forms.utils import add_blank_choice
|
||||
from utilities.forms.widgets import DatePicker, DateTimePicker
|
||||
from utilities.forms.widgets import APISelect, APISelectMultiple, DatePicker, DateTimePicker
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.validators import validate_regex
|
||||
|
||||
@ -410,7 +411,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
|
||||
# Select
|
||||
elif self.type in (CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT):
|
||||
choices = [(c, c) for c in self.choices]
|
||||
choices = self.choice_set.choices
|
||||
default_choice = self.default if self.default in self.choices else None
|
||||
|
||||
if not required or default_choice is None:
|
||||
@ -421,11 +422,17 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
initial = default_choice
|
||||
|
||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
|
||||
field = field_class(choices=choices, required=required, initial=initial)
|
||||
field_class = CSVChoiceField if for_csv_import else DynamicChoiceField
|
||||
widget_class = APISelect
|
||||
else:
|
||||
field_class = CSVMultipleChoiceField if for_csv_import else forms.MultipleChoiceField
|
||||
field = field_class(choices=choices, required=required, initial=initial)
|
||||
field_class = CSVMultipleChoiceField if for_csv_import else DynamicMultipleChoiceField
|
||||
widget_class = APISelectMultiple
|
||||
field = field_class(
|
||||
choices=choices,
|
||||
required=required,
|
||||
initial=initial,
|
||||
widget=widget_class(api_url=f'/api/extras/custom-field-choices/{self.choice_set.pk}/choices/')
|
||||
)
|
||||
|
||||
# URL
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_URL:
|
||||
@ -604,14 +611,14 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
|
||||
# Validate selected choice
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
if value not in self.choices:
|
||||
if value not in [c[0] for c in self.choices]:
|
||||
raise ValidationError(
|
||||
f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}"
|
||||
)
|
||||
|
||||
# Validate all selected choices
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||
if not set(value).issubset(self.choices):
|
||||
if not set(value).issubset([c[0] for c in self.choices]):
|
||||
raise ValidationError(
|
||||
f"Invalid choice(s) ({', '.join(value)}). Available choices are: {', '.join(self.choices)}"
|
||||
)
|
||||
@ -645,13 +652,23 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
base_choices = models.CharField(
|
||||
max_length=50,
|
||||
choices=CustomFieldChoiceSetBaseChoices,
|
||||
blank=True,
|
||||
help_text=_('Base set of predefined choices (optional)')
|
||||
)
|
||||
extra_choices = ArrayField(
|
||||
base_field=models.CharField(max_length=100),
|
||||
help_text=_('List of field choices')
|
||||
ArrayField(
|
||||
base_field=models.CharField(max_length=100),
|
||||
size=2
|
||||
),
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
order_alphabetically = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_('Choices are automatically ordered alphabetically on save')
|
||||
help_text=_('Choices are automatically ordered alphabetically')
|
||||
)
|
||||
|
||||
clone_fields = ('extra_choices', 'order_alphabetically')
|
||||
@ -667,16 +684,31 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
||||
|
||||
@property
|
||||
def choices(self):
|
||||
return self.extra_choices
|
||||
"""
|
||||
Returns a concatenation of the base and extra choices.
|
||||
"""
|
||||
if not hasattr(self, '_choices'):
|
||||
self._choices = []
|
||||
if self.base_choices:
|
||||
self._choices.extend(CHOICE_SETS.get(self.base_choices))
|
||||
if self.extra_choices:
|
||||
self._choices.extend(self.extra_choices)
|
||||
if self.order_alphabetically:
|
||||
self._choices = sorted(self._choices, key=lambda x: x[0])
|
||||
return self._choices
|
||||
|
||||
@property
|
||||
def choices_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
def clean(self):
|
||||
if not self.base_choices and not self.extra_choices:
|
||||
raise ValidationError(_("Must define base or extra choices."))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
# Sort choices if alphabetical ordering is enforced
|
||||
if self.order_alphabetically:
|
||||
self.extra_choices = sorted(self.choices)
|
||||
self.extra_choices = sorted(self.extra_choices, key=lambda x: x[0])
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
Reference in New Issue
Block a user