mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Closes #5451: Add support for multiple-selection custom fields
This commit is contained in:
parent
68940ae03e
commit
e22ff1643a
@ -16,6 +16,7 @@ Custom fields must be created through the admin UI under Extras > Custom Fields.
|
|||||||
* Date: A date in ISO 8601 format (YYYY-MM-DD)
|
* Date: A date in ISO 8601 format (YYYY-MM-DD)
|
||||||
* URL: This will be presented as a link in the web UI
|
* URL: This will be presented as a link in the web UI
|
||||||
* Selection: A selection of one of several pre-defined custom choices
|
* Selection: A selection of one of several pre-defined custom choices
|
||||||
|
* Multiple selection: A selection field which supports the assignment of multiple values
|
||||||
|
|
||||||
Each custom field must have a name; this should be a simple database-friendly string, e.g. `tps_report`. You may also assign a corresponding human-friendly label (e.g. "TPS report"); the label will be displayed on web forms. A weight is also required: Higher-weight fields will be ordered lower within a form. (The default weight is 100.) If a description is provided, it will appear beneath the field in a form.
|
Each custom field must have a name; this should be a simple database-friendly string, e.g. `tps_report`. You may also assign a corresponding human-friendly label (e.g. "TPS report"); the label will be displayed on web forms. A weight is also required: Higher-weight fields will be ordered lower within a form. (The default weight is 100.) If a description is provided, it will appear beneath the field in a form.
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ Each custom selection field must have at least two choices. These are specified
|
|||||||
|
|
||||||
If a default value is specified for a selection field, it must exactly match one of the provided choices.
|
If a default value is specified for a selection field, it must exactly match one of the provided choices.
|
||||||
|
|
||||||
|
The value of a multiple selection field will always return a list, even if only one value is selected.
|
||||||
|
|
||||||
## Custom Fields and the REST API
|
## Custom Fields and the REST API
|
||||||
|
|
||||||
When retrieving an object via the REST API, all of its custom data will be included within the `custom_fields` attribute. For example, below is the partial output of a site with two custom fields defined:
|
When retrieving an object via the REST API, all of its custom data will be included within the `custom_fields` attribute. For example, below is the partial output of a site with two custom fields defined:
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
* [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models
|
* [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models
|
||||||
* [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
|
* [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
|
||||||
|
* [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields
|
||||||
|
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ class CustomFieldTypeChoices(ChoiceSet):
|
|||||||
TYPE_DATE = 'date'
|
TYPE_DATE = 'date'
|
||||||
TYPE_URL = 'url'
|
TYPE_URL = 'url'
|
||||||
TYPE_SELECT = 'select'
|
TYPE_SELECT = 'select'
|
||||||
|
TYPE_MULTISELECT = 'multiselect'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(TYPE_TEXT, 'Text'),
|
(TYPE_TEXT, 'Text'),
|
||||||
@ -21,6 +22,7 @@ class CustomFieldTypeChoices(ChoiceSet):
|
|||||||
(TYPE_DATE, 'Date'),
|
(TYPE_DATE, 'Date'),
|
||||||
(TYPE_URL, 'URL'),
|
(TYPE_URL, 'URL'),
|
||||||
(TYPE_SELECT, 'Selection'),
|
(TYPE_SELECT, 'Selection'),
|
||||||
|
(TYPE_MULTISELECT, 'Multiple selection'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ from django.utils.safestring import mark_safe
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from netbox.models import BigIDModel
|
from netbox.models import BigIDModel
|
||||||
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
from utilities.forms import (
|
||||||
|
CSVChoiceField, DatePicker, LaxURLField, StaticSelect2Multiple, StaticSelect2, add_blank_choice,
|
||||||
|
)
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import validate_regex
|
from utilities.validators import validate_regex
|
||||||
|
|
||||||
@ -153,7 +155,10 @@ class CustomField(BigIDModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Choices can be set only on selection fields
|
# Choices can be set only on selection fields
|
||||||
if self.choices and self.type != CustomFieldTypeChoices.TYPE_SELECT:
|
if self.choices and self.type not in (
|
||||||
|
CustomFieldTypeChoices.TYPE_SELECT,
|
||||||
|
CustomFieldTypeChoices.TYPE_MULTISELECT
|
||||||
|
):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'choices': "Choices may be set only for custom selection fields."
|
'choices': "Choices may be set only for custom selection fields."
|
||||||
})
|
})
|
||||||
@ -206,7 +211,7 @@ class CustomField(BigIDModel):
|
|||||||
field = forms.DateField(required=required, initial=initial, widget=DatePicker())
|
field = forms.DateField(required=required, initial=initial, widget=DatePicker())
|
||||||
|
|
||||||
# Select
|
# Select
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
elif self.type in (CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT):
|
||||||
choices = [(c, c) for c in self.choices]
|
choices = [(c, c) for c in self.choices]
|
||||||
default_choice = self.default if self.default in self.choices else None
|
default_choice = self.default if self.default in self.choices else None
|
||||||
|
|
||||||
@ -217,10 +222,16 @@ class CustomField(BigIDModel):
|
|||||||
if set_initial and default_choice:
|
if set_initial and default_choice:
|
||||||
initial = default_choice
|
initial = default_choice
|
||||||
|
|
||||||
field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
field = field_class(
|
field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
|
||||||
choices=choices, required=required, initial=initial, widget=StaticSelect2()
|
field = field_class(
|
||||||
)
|
choices=choices, required=required, initial=initial, widget=StaticSelect2()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
field_class = CSVChoiceField if for_csv_import else forms.MultipleChoiceField
|
||||||
|
field = field_class(
|
||||||
|
choices=choices, required=required, initial=initial, widget=StaticSelect2Multiple()
|
||||||
|
)
|
||||||
|
|
||||||
# URL
|
# URL
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_URL:
|
elif self.type == CustomFieldTypeChoices.TYPE_URL:
|
||||||
@ -285,5 +296,12 @@ class CustomField(BigIDModel):
|
|||||||
f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}"
|
f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Validate all selected choices
|
||||||
|
if 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)}"
|
||||||
|
)
|
||||||
|
|
||||||
elif self.required:
|
elif self.required:
|
||||||
raise ValidationError("Required field cannot be empty.")
|
raise ValidationError("Required field cannot be empty.")
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
<i class="mdi mdi-close-thick text-danger" title="False"></i>
|
<i class="mdi mdi-close-thick text-danger" title="False"></i>
|
||||||
{% elif field.type == 'url' and value %}
|
{% elif field.type == 'url' and value %}
|
||||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||||
|
{% elif field.type == 'multiselect' and value %}
|
||||||
|
{{ value|join:", " }}
|
||||||
{% elif value is not None %}
|
{% elif value is not None %}
|
||||||
{{ value }}
|
{{ value }}
|
||||||
{% elif field.required %}
|
{% elif field.required %}
|
||||||
|
Loading…
Reference in New Issue
Block a user