diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 68edc93f6..d0163e988 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -2,6 +2,8 @@ import django_filters from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field from circuits.models import CircuitTermination from extras.filtersets import LocalConfigContextFilterSet @@ -818,6 +820,10 @@ class PlatformFilterSet(OrganizationalModelFilterSet): to_field_name='slug', label=_('Manufacturer (slug)'), ) + available_for_device_type = django_filters.ModelChoiceFilter( + queryset=DeviceType.objects.all(), + method='get_for_device_type' + ) config_template_id = django_filters.ModelMultipleChoiceFilter( queryset=ConfigTemplate.objects.all(), label=_('Config template (ID)'), @@ -827,6 +833,14 @@ class PlatformFilterSet(OrganizationalModelFilterSet): model = Platform fields = ['id', 'name', 'slug', 'description'] + @extend_schema_field(OpenApiTypes.STR) + def get_for_device_type(self, queryset, name, value): + """ + Return all Platforms available for a specific manufacturer based on device type and Platforms not assigned any + manufacturer + """ + return queryset.filter(Q(manufacturer=None) | Q(manufacturer__device_types=value)) + class DeviceFilterSet( NetBoxModelFilterSet, diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index da3a2bea4..6773bc55f 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -291,7 +291,11 @@ class DeviceTypeForm(NetBoxModelForm): default_platform = DynamicModelChoiceField( label=_('Default platform'), queryset=Platform.objects.all(), - required=False + required=False, + selector=True, + query_params={ + 'manufacturer_id': ['$manufacturer', 'null'], + } ) slug = SlugField( label=_('Slug'), @@ -444,7 +448,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm): label=_('Platform'), queryset=Platform.objects.all(), required=False, - selector=True + selector=True, + query_params={ + 'available_for_device_type': '$device_type', + } ) cluster = DynamicModelChoiceField( label=_('Cluster'), diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 89d15a0ef..b255c283e 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -1787,6 +1787,7 @@ class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests): Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0], description='foobar1'), Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1], description='foobar2'), Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2], description='foobar3'), + Platform(name='Platform 4', slug='platform-4'), ) Platform.objects.bulk_create(platforms) @@ -1813,6 +1814,17 @@ class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_available_for_device_type(self): + manufacturers = Manufacturer.objects.all()[:2] + device_type = DeviceType.objects.create( + manufacturer=manufacturers[0], + model='Device Type 1', + slug='device-type-1', + u_height=1 + ) + params = {'available_for_device_type': device_type.pk} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Device.objects.all()