diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index f5c27f7d3..7a4b9aaff 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1672,7 +1672,10 @@ class PlatformCSVForm(CSVModelForm): class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): region = DynamicModelChoiceField( queryset=Region.objects.all(), - required=False + required=False, + initial_params={ + 'sites': '$site' + } ) site = DynamicModelChoiceField( queryset=Site.objects.all(), @@ -1686,6 +1689,9 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): display_field='display_name', query_params={ 'site_id': '$site' + }, + initial_params={ + 'racks': '$rack' } ) rack = DynamicModelChoiceField( @@ -1711,7 +1717,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): ) manufacturer = DynamicModelChoiceField( queryset=Manufacturer.objects.all(), - required=False + required=False, + initial_params={ + 'device_types': '$device_type' + } ) device_type = DynamicModelChoiceField( queryset=DeviceType.objects.all(), @@ -1733,7 +1742,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): cluster_group = DynamicModelChoiceField( queryset=ClusterGroup.objects.all(), required=False, - null_option='None' + null_option='None', + initial_params={ + 'clusters': '$cluster' + } ) cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), @@ -1772,27 +1784,6 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): } def __init__(self, *args, **kwargs): - - # Initialize helper selectors - instance = kwargs.get('instance') - if 'initial' not in kwargs: - kwargs['initial'] = {} - # Using hasattr() instead of "is not None" to avoid RelatedObjectDoesNotExist on required field - if instance and hasattr(instance, 'device_type'): - kwargs['initial']['manufacturer'] = instance.device_type.manufacturer - if instance and instance.cluster is not None: - kwargs['initial']['cluster_group'] = instance.cluster.group - - if 'device_type' in kwargs['initial'] and 'manufacturer' not in kwargs['initial']: - device_type_id = kwargs['initial']['device_type'] - manufacturer_id = DeviceType.objects.filter(pk=device_type_id).values_list('manufacturer__pk', flat=True).first() - kwargs['initial']['manufacturer'] = manufacturer_id - - if 'cluster' in kwargs['initial'] and 'cluster_group' not in kwargs['initial']: - cluster_id = kwargs['initial']['cluster'] - cluster_group_id = Cluster.objects.filter(pk=cluster_id).values_list('group__pk', flat=True).first() - kwargs['initial']['cluster_group'] = cluster_group_id - super().__init__(*args, **kwargs) if self.instance.pk: diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 6146e00d3..982b80323 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -248,6 +248,7 @@ class DynamicModelChoiceMixin: """ :param display_field: The name of the attribute of an API response object to display in the selection list :param query_params: A dictionary of additional key/value pairs to attach to the API request + :param initial_params: A dictionary of child field references to use for selecting a parent field's initial value :param null_option: The string used to represent a null selection (if any) :param disabled_indicator: The name of the field which, if populated, will disable selection of the choice (optional) @@ -256,10 +257,11 @@ class DynamicModelChoiceMixin: filter = django_filters.ModelChoiceFilter widget = widgets.APISelect - def __init__(self, display_field='name', query_params=None, null_option=None, disabled_indicator=None, - brief_mode=True, *args, **kwargs): + def __init__(self, display_field='name', query_params=None, initial_params=None, null_option=None, + disabled_indicator=None, brief_mode=True, *args, **kwargs): self.display_field = display_field self.query_params = query_params or {} + self.initial_params = initial_params or {} self.null_option = null_option self.disabled_indicator = disabled_indicator self.brief_mode = brief_mode @@ -300,6 +302,15 @@ class DynamicModelChoiceMixin: def get_bound_field(self, form, field_name): bound_field = BoundField(form, self, field_name) + # Set initial value based on prescribed child fields (if not already set) + if not self.initial and self.initial_params: + filter_kwargs = {} + for kwarg, child_field in self.initial_params.items(): + value = form.initial.get(child_field.lstrip('$')) + if value: + filter_kwargs[kwarg] = value + self.initial = self.queryset.filter(**filter_kwargs).first() + # Modify the QuerySet of the field before we return it. Limit choices to any data already bound: Options # will be populated on-demand via the APISelect widget. data = bound_field.value()