diff --git a/docs/customization/custom-fields.md b/docs/customization/custom-fields.md index 1f9a4a8bfd9..1e500d203c3 100644 --- a/docs/customization/custom-fields.md +++ b/docs/customization/custom-fields.md @@ -74,6 +74,8 @@ If a default value is specified for a selection field, it must exactly match one An object or multi-object custom field can be used to refer to a particular NetBox object or objects as the "value" for a custom field. These custom fields must define an `object_type`, which determines the type of object to which custom field instances point. +By default, an object choice field will make all objects of that type available for selection in the drop-down. The list choices can be filtered to only show objects with certain values by providing a `query_params` dict in the Related Object Filter field, as a JSON value. More information about `query_params` can be found [here](./custom-scripts.md#objectvar) + ## Custom Fields in Templates Several features within NetBox, such as export templates and webhooks, utilize Jinja2 templating. For convenience, objects which support custom field assignment expose custom field data through the `cf` property. This is a bit cleaner than accessing custom field data through the actual field (`custom_field_data`). diff --git a/netbox/extras/api/serializers_/customfields.py b/netbox/extras/api/serializers_/customfields.py index 082047e94a4..0f393556c5e 100644 --- a/netbox/extras/api/serializers_/customfields.py +++ b/netbox/extras/api/serializers_/customfields.py @@ -64,7 +64,7 @@ class Meta: fields = [ 'id', 'url', 'display', 'object_types', 'type', 'related_object_type', 'data_type', 'name', 'label', 'group_name', 'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', - 'is_cloneable', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', + 'is_cloneable', 'default', 'related_object_filter', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'comments', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'name', 'description') diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index ebd6e6c08ba..5c6bea9a8a1 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -63,7 +63,7 @@ class CustomFieldForm(forms.ModelForm): FieldSet( 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable', name=_('Behavior') ), - FieldSet('default', 'choice_set', name=_('Values')), + FieldSet('default', 'choice_set', 'related_object_filter', name=_('Values')), FieldSet('validation_minimum', 'validation_maximum', 'validation_regex', name=_('Validation')), ) diff --git a/netbox/extras/migrations/0116_customfield_related_object_filter.py b/netbox/extras/migrations/0116_customfield_related_object_filter.py new file mode 100644 index 00000000000..6bf2abdebe4 --- /dev/null +++ b/netbox/extras/migrations/0116_customfield_related_object_filter.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-19 07:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0115_convert_dashboard_widgets'), + ] + + operations = [ + migrations.AddField( + model_name='customfield', + name='related_object_filter', + field=models.JSONField(blank=True, null=True), + ), + ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index d8f02ec6cd4..c1ebf380494 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -153,6 +153,14 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): 'Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. "Foo").' ) ) + related_object_filter = models.JSONField( + blank=True, + null=True, + help_text=_( + 'Filter the object selection choices using a query_params dict (must be a JSON value).' + 'Encapsulate strings with double quotes (e.g. "Foo").' + ) + ) weight = models.PositiveSmallIntegerField( default=100, verbose_name=_('display weight'), @@ -499,7 +507,8 @@ def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibil field = field_class( queryset=model.objects.all(), required=required, - initial=initial + initial=initial, + query_params=self.related_object_filter ) # Multiple objects @@ -510,6 +519,7 @@ def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibil queryset=model.objects.all(), required=required, initial=initial, + query_params=self.related_object_filter ) # Text diff --git a/netbox/extras/tests/test_filtersets.py b/netbox/extras/tests/test_filtersets.py index b68c02efce4..88cfc477701 100644 --- a/netbox/extras/tests/test_filtersets.py +++ b/netbox/extras/tests/test_filtersets.py @@ -26,7 +26,7 @@ class CustomFieldTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = CustomField.objects.all() filterset = CustomFieldFilterSet - ignore_fields = ('default',) + ignore_fields = ('default', 'related_object_filter') @classmethod def setUpTestData(cls): diff --git a/netbox/templates/extras/customfield.html b/netbox/templates/extras/customfield.html index 4efd1e4d0c2..86883347d9b 100644 --- a/netbox/templates/extras/customfield.html +++ b/netbox/templates/extras/customfield.html @@ -52,6 +52,10 @@