diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md
index d1b6b4cda..cefab428e 100644
--- a/docs/release-notes/version-3.3.md
+++ b/docs/release-notes/version-3.3.md
@@ -12,6 +12,7 @@
* [#8471](https://github.com/netbox-community/netbox/issues/8471) - Add `status` field to Cluster
* [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
* [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results
+* [#9166](https://github.com/netbox-community/netbox/issues/9166) - Add UI visibility toggle for custom fields
### Other Changes
@@ -20,7 +21,7 @@
### REST API Changes
* extras.CustomField
- * Added `group_name` field
+ * Added `group_name` and `ui_visibility` fields
* ipam.IPAddress
* The `nat_inside` field no longer requires a unique value
* The `nat_outside` field has changed from a single IP address instance to a list of multiple IP addresses
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index 1a26faec1..cb317d6c7 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -90,8 +90,8 @@ class CustomFieldSerializer(ValidatedModelSerializer):
model = CustomField
fields = [
'id', 'url', 'display', 'content_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
- 'description', 'required', 'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum',
- 'validation_regex', 'choices', 'created', 'last_updated', 'ui_visibility',
+ 'description', 'required', 'filter_logic', 'ui_visibility', 'default', 'weight', 'validation_minimum',
+ 'validation_maximum', 'validation_regex', 'choices', 'created', 'last_updated',
]
def get_data_type(self, obj):
diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py
index ea74dfc82..b59e28018 100644
--- a/netbox/extras/filtersets.py
+++ b/netbox/extras/filtersets.py
@@ -63,7 +63,8 @@ class CustomFieldFilterSet(BaseFilterSet):
class Meta:
model = CustomField
fields = [
- 'id', 'content_types', 'name', 'group_name', 'required', 'filter_logic', 'weight', 'description', 'ui_visibility'
+ 'id', 'content_types', 'name', 'group_name', 'required', 'filter_logic', 'ui_visibility', 'weight',
+ 'description',
]
def search(self, queryset, name, value):
diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py
index c0483d36e..95de7a2fe 100644
--- a/netbox/extras/forms/bulk_import.py
+++ b/netbox/extras/forms/bulk_import.py
@@ -37,7 +37,8 @@ class CustomFieldCSVForm(CSVModelForm):
model = CustomField
fields = (
'name', 'label', 'group_name', 'type', 'content_types', 'required', 'description', 'weight', 'filter_logic',
- 'default', 'choices', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'ui_visibility',
+ 'default', 'choices', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
+ 'ui_visibility',
)
diff --git a/netbox/extras/forms/customfields.py b/netbox/extras/forms/customfields.py
index c4496c5f8..4cf8b5e0a 100644
--- a/netbox/extras/forms/customfields.py
+++ b/netbox/extras/forms/customfields.py
@@ -51,6 +51,10 @@ class CustomFieldsMixin:
if customfield.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY:
self.fields[field_name].disabled = True
+ if self.fields[field_name].help_text:
+ self.fields[field_name].help_text += '
'
+ self.fields[field_name].help_text += ' ' \
+ 'Field is set to read-only.'
# Annotate the field in the list of CustomField form fields
self.custom_fields[field_name] = customfield
diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py
index cd59a9db1..aaeb45dbe 100644
--- a/netbox/extras/forms/filtersets.py
+++ b/netbox/extras/forms/filtersets.py
@@ -59,7 +59,7 @@ class CustomFieldFilterForm(FilterForm):
ui_visibility = forms.ChoiceField(
choices=add_blank_choice(CustomFieldVisibilityChoices),
required=False,
- label=_('UI Visibility'),
+ label=_('UI visibility'),
widget=StaticSelect()
)
diff --git a/netbox/extras/forms/models.py b/netbox/extras/forms/models.py
index 16874c49e..ab423e2fb 100644
--- a/netbox/extras/forms/models.py
+++ b/netbox/extras/forms/models.py
@@ -41,9 +41,9 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
fieldsets = (
('Custom Field', (
- 'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'weight', 'required', 'description', 'ui_visibility',
+ 'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'weight', 'required', 'description',
)),
- ('Behavior', ('filter_logic',)),
+ ('Behavior', ('filter_logic', 'ui_visibility')),
('Values', ('default', 'choices')),
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
)
diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py
index c48b6895c..c91f96c15 100644
--- a/netbox/extras/models/customfields.py
+++ b/netbox/extras/models/customfields.py
@@ -140,7 +140,8 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
max_length=50,
choices=CustomFieldVisibilityChoices,
default=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
- help_text='Specifies the visibility of custom field in the UI.'
+ verbose_name='UI visibility',
+ help_text='Specifies the visibility of custom field in the UI'
)
objects = CustomFieldManager()
diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py
index d294fd231..540034696 100644
--- a/netbox/extras/tables/tables.py
+++ b/netbox/extras/tables/tables.py
@@ -34,7 +34,7 @@ class CustomFieldTable(NetBoxTable):
model = CustomField
fields = (
'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'weight', 'default',
- 'description', 'filter_logic', 'choices', 'created', 'last_updated', 'ui_visibility',
+ 'description', 'filter_logic', 'ui_visibility', 'choices', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description')
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
index 76b546192..817da526b 100644
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -125,7 +125,7 @@ class CustomFieldsMixin(models.Model):
def get_custom_fields_by_group(self):
"""
- Return a dictionary of custom field/value mappings organized by group.
+ Return a dictionary of custom field/value mappings organized by group. Hidden fields are omitted.
"""
grouped_custom_fields = defaultdict(dict)
for cf, value in self.get_custom_fields(omit_hidden=True).items():
diff --git a/netbox/templates/extras/customfield.html b/netbox/templates/extras/customfield.html
index 72dc2e4c3..aca0b5012 100644
--- a/netbox/templates/extras/customfield.html
+++ b/netbox/templates/extras/customfield.html
@@ -42,6 +42,10 @@