diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md
index 7a5553b41..d4d2d35f5 100644
--- a/docs/release-notes/version-3.3.md
+++ b/docs/release-notes/version-3.3.md
@@ -8,6 +8,7 @@
* [#10435](https://github.com/netbox-community/netbox/issues/10435) - Fix exception when filtering VLANs by virtual machine with no cluster assigned
* [#10439](https://github.com/netbox-community/netbox/issues/10439) - Fix form widget styling for DeviceType airflow field
* [#10445](https://github.com/netbox-community/netbox/issues/10445) - Avoid rounding virtual machine memory values
+* [#10461](https://github.com/netbox-community/netbox/issues/10461) - Enable filtering by read-only custom fields in the UI
---
diff --git a/netbox/extras/forms/customfields.py b/netbox/extras/forms/customfields.py
index 7574f4f2b..40d068450 100644
--- a/netbox/extras/forms/customfields.py
+++ b/netbox/extras/forms/customfields.py
@@ -34,7 +34,9 @@ class CustomFieldsMixin:
return ContentType.objects.get_for_model(self.model)
def _get_custom_fields(self, content_type):
- return CustomField.objects.filter(content_types=content_type)
+ return CustomField.objects.filter(content_types=content_type).exclude(
+ ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN
+ )
def _get_form_field(self, customfield):
return customfield.to_form_field()
@@ -50,13 +52,6 @@ class CustomFieldsMixin:
field_name = f'cf_{customfield.name}'
self.fields[field_name] = self._get_form_field(customfield)
- 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
if customfield.group_name not in self.custom_field_groups:
diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py
index 43c4f9671..d52d73848 100644
--- a/netbox/extras/models/customfields.py
+++ b/netbox/extras/models/customfields.py
@@ -297,12 +297,13 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
return model.objects.filter(pk__in=value)
return value
- def to_form_field(self, set_initial=True, enforce_required=True, for_csv_import=False):
+ def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibility=True, for_csv_import=False):
"""
Return a form field suitable for setting a CustomField's value for an object.
set_initial: Set initial data for the field. This should be False when generating a field for bulk editing.
enforce_required: Honor the value of CustomField.required. Set to False for filtering/bulk editing.
+ enforce_visibility: Honor the value of CustomField.ui_visibility. Set to False for filtering.
for_csv_import: Return a form field suitable for bulk import of objects in CSV format.
"""
initial = self.default if set_initial else None
@@ -398,6 +399,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
if self.description:
field.help_text = escape(self.description)
+ # Annotate read-only fields
+ if enforce_visibility and self.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY:
+ field.disabled = True
+ prepend = '
' if field.help_text else ''
+ field.help_text += f'{prepend} Field is set to read-only.'
+
return field
def to_filter(self, lookup_expr=None):
diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py
index 2676e4cde..fa741faf7 100644
--- a/netbox/netbox/forms/base.py
+++ b/netbox/netbox/forms/base.py
@@ -2,7 +2,7 @@ from django import forms
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
-from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices
+from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
from extras.forms.customfields import CustomFieldsMixin
from extras.models import CustomField, Tag
from utilities.forms import BootstrapMixin, CSVModelForm
@@ -125,10 +125,10 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
)
def _get_custom_fields(self, content_type):
- return CustomField.objects.filter(content_types=content_type).exclude(
+ return super()._get_custom_fields(content_type).exclude(
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
Q(type=CustomFieldTypeChoices.TYPE_JSON)
)
def _get_form_field(self, customfield):
- return customfield.to_form_field(set_initial=False, enforce_required=False)
+ return customfield.to_form_field(set_initial=False, enforce_required=False, enforce_visibility=False)