diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index 2b1d49b33..1ef4d1e1d 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -23,12 +23,9 @@ class CustomFieldDefaultValues: def __call__(self, serializer_field): self.model = serializer_field.parent.Meta.model - # Retrieve the CustomFields for the parent model - fields = CustomField.objects.get_for_model(self.model) - - # Populate the default value for each CustomField + # Populate the default value for each CustomField on the model value = {} - for field in fields: + for field in CustomField.objects.get_for_model(self.model): if field.default is not None: value[field.name] = field.default else: diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index a29036821..67155a90b 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -69,8 +69,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)): custom_fields = self.get_queryset().filter(object_types=content_type) # Populate the request cache to avoid redundant lookups - if cache is not None: - cache['custom_fields'][model._meta.model] = custom_fields + cache['custom_fields'][model._meta.model] = custom_fields return custom_fields diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index e33b114b5..33f7cb4a3 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -305,17 +305,13 @@ class NetBoxModelFilterSet(ChangeLoggedModelFilterSet): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Dynamically add a Filter for each CustomField applicable to the parent model - custom_fields = [ - cf for cf in CustomField.objects.get_for_model(self._meta.model) - if cf.filter_logic != CustomFieldFilterLogicChoices.FILTER_DISABLED - ] - custom_field_filters = {} - for custom_field in custom_fields: - filter_name = f'cf_{custom_field.name}' - filter_instance = custom_field.to_filter() - if filter_instance: + for custom_field in CustomField.objects.get_for_model(self._meta.model): + if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_DISABLED: + # Skip disabled fields + continue + if filter_instance := custom_field.to_filter(): + filter_name = f'cf_{custom_field.name}' custom_field_filters[filter_name] = filter_instance # Add relevant additional lookups diff --git a/netbox/netbox/forms/bulk_import.py b/netbox/netbox/forms/bulk_import.py index ed01fd34f..c5609a2a7 100644 --- a/netbox/netbox/forms/bulk_import.py +++ b/netbox/netbox/forms/bulk_import.py @@ -31,6 +31,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm): ) def _get_custom_fields(self, content_type): + # Return only custom fields that are editable in the UI return [ cf for cf in CustomField.objects.get_for_model(content_type.model_class()) if cf.ui_editable == CustomFieldUIEditableChoices.YES diff --git a/netbox/netbox/forms/filtersets.py b/netbox/netbox/forms/filtersets.py index f524d3fd3..8ff20264e 100644 --- a/netbox/netbox/forms/filtersets.py +++ b/netbox/netbox/forms/filtersets.py @@ -34,6 +34,7 @@ class NetBoxModelFilterSetForm(FilterModifierMixin, CustomFieldsMixin, SavedFilt selector_fields = ('filter_id', 'q') def _get_custom_fields(self, content_type): + # Return only non-hidden custom fields for which filtering is enabled (excluding JSON fields) return [ cf for cf in super()._get_custom_fields(content_type) if ( cf.filter_logic != CustomFieldFilterLogicChoices.FILTER_DISABLED and diff --git a/netbox/netbox/forms/mixins.py b/netbox/netbox/forms/mixins.py index c1cf4bcac..1460c86ba 100644 --- a/netbox/netbox/forms/mixins.py +++ b/netbox/netbox/forms/mixins.py @@ -65,6 +65,7 @@ class CustomFieldsMixin: return ObjectType.objects.get_for_model(self.model) def _get_custom_fields(self, content_type): + # Return only custom fields that are not hidden from the UI return [ cf for cf in CustomField.objects.get_for_model(content_type.model_class()) if cf.ui_editable != CustomFieldUIEditableChoices.HIDDEN diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index c17238a97..d01669186 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -321,7 +321,7 @@ class CustomFieldsMixin(models.Model): def save(self, *args, **kwargs): from extras.models import CustomField - # Populate default values if omitted + # Populate default values for custom fields not already present in the object data for cf in CustomField.objects.get_for_model(self): if cf.name not in self.custom_field_data and cf.default is not None: self.custom_field_data[cf.name] = cf.default diff --git a/netbox/netbox/search/backends.py b/netbox/netbox/search/backends.py index 4fed39543..cd3c277e2 100644 --- a/netbox/netbox/search/backends.py +++ b/netbox/netbox/search/backends.py @@ -209,7 +209,6 @@ class CachedValueSearchBackend(SearchBackend): break # Prefetch any associated custom fields (excluding those with a zero search weight) - object_type = ObjectType.objects.get_for_model(indexer.model) custom_fields = [ cf for cf in CustomField.objects.get_for_model(indexer.model) if cf.search_weight > 0 @@ -220,6 +219,7 @@ class CachedValueSearchBackend(SearchBackend): self.remove(instance) # Generate cache data + ObjectType.objects.get_for_model(indexer.model) for field in indexer.to_cache(instance, custom_fields=custom_fields): buffer.append( CachedValue( diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 168925e12..a1ac0a3e4 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -242,8 +242,7 @@ class NetBoxTable(BaseTable): (name, deepcopy(column)) for name, column in registered_columns.items() ]) - # Add custom field & custom link columns - object_type = ObjectType.objects.get_for_model(self._meta.model) + # Add columns for custom fields custom_fields = [ cf for cf in CustomField.objects.get_for_model(self._meta.model) if cf.ui_visible != CustomFieldUIVisibleChoices.HIDDEN @@ -251,6 +250,9 @@ class NetBoxTable(BaseTable): extra_columns.extend([ (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields ]) + + # Add columns for custom links + object_type = ObjectType.objects.get_for_model(self._meta.model) custom_links = CustomLink.objects.filter(object_types=object_type, enabled=True) extra_columns.extend([ (f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in custom_links diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index b79a50a0b..4e6f8c343 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -483,12 +483,11 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): else: instance = self.queryset.model() - # For newly created objects, apply any default custom field values - custom_fields = [ - cf for cf in CustomField.objects.get_for_model(self.queryset.model) - if cf.ui_editable == CustomFieldUIEditableChoices.YES - ] - for cf in custom_fields: + # For newly created objects, apply any default values for custom fields + for cf in CustomField.objects.get_for_model(self.queryset.model): + if cf.ui_editable != CustomFieldUIEditableChoices.YES: + # Skip custom fields which are not editable via the UI + continue field_name = f'cf_{cf.name}' if field_name not in record: record[field_name] = cf.default