mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
Merge pull request #5308 from netbox-community/5271-DynamicModelChoiceMixin-initial
Fixes #5271: Extend DynamicModelChoiceMixin to accept initial field values implied by child fields
This commit is contained in:
commit
a69d61c124
@ -1672,7 +1672,10 @@ class PlatformCSVForm(CSVModelForm):
|
|||||||
class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
initial_params={
|
||||||
|
'sites': '$site'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
@ -1686,6 +1689,9 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
|
},
|
||||||
|
initial_params={
|
||||||
|
'racks': '$rack'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack = DynamicModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
@ -1711,7 +1717,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
)
|
)
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
initial_params={
|
||||||
|
'device_types': '$device_type'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
@ -1733,7 +1742,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
cluster_group = DynamicModelChoiceField(
|
cluster_group = DynamicModelChoiceField(
|
||||||
queryset=ClusterGroup.objects.all(),
|
queryset=ClusterGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None'
|
null_option='None',
|
||||||
|
initial_params={
|
||||||
|
'clusters': '$cluster'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
cluster = DynamicModelChoiceField(
|
cluster = DynamicModelChoiceField(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
@ -1772,27 +1784,6 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
@ -4289,7 +4280,10 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
initial_params={
|
||||||
|
'powerpanel': '$power_panel'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
power_panel = DynamicModelChoiceField(
|
power_panel = DynamicModelChoiceField(
|
||||||
queryset=PowerPanel.objects.all(),
|
queryset=PowerPanel.objects.all(),
|
||||||
@ -4324,14 +4318,6 @@ class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
'phase': StaticSelect2(),
|
'phase': StaticSelect2(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# Initialize site field
|
|
||||||
if self.instance and hasattr(self.instance, 'power_panel'):
|
|
||||||
self.initial['site'] = self.instance.power_panel.site
|
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedCSVForm(CustomFieldModelCSVForm):
|
class PowerFeedCSVForm(CustomFieldModelCSVForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
|
@ -100,23 +100,6 @@ class DeviceTestCase(TestCase):
|
|||||||
self.assertIn('face', form.errors)
|
self.assertIn('face', form.errors)
|
||||||
self.assertIn('position', form.errors)
|
self.assertIn('position', form.errors)
|
||||||
|
|
||||||
def test_initial_data_population(self):
|
|
||||||
device_type = DeviceType.objects.first()
|
|
||||||
cluster = Cluster.objects.first()
|
|
||||||
test = DeviceForm(initial={
|
|
||||||
'device_type': device_type.pk,
|
|
||||||
'device_role': DeviceRole.objects.first().pk,
|
|
||||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
|
||||||
'site': Site.objects.first().pk,
|
|
||||||
'cluster': cluster.pk,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Check that the initial value for the manufacturer is set automatically when assigning the device type
|
|
||||||
self.assertEqual(test.initial['manufacturer'], device_type.manufacturer.pk)
|
|
||||||
|
|
||||||
# Check that the initial value for the cluster group is set automatically when assigning the cluster
|
|
||||||
self.assertEqual(test.initial['cluster_group'], cluster.group.pk)
|
|
||||||
|
|
||||||
|
|
||||||
class LabelTestCase(TestCase):
|
class LabelTestCase(TestCase):
|
||||||
|
|
||||||
|
@ -265,6 +265,9 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
null_option='None',
|
null_option='None',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
|
},
|
||||||
|
initial_params={
|
||||||
|
'vlans': '$vlan'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
vlan = DynamicModelChoiceField(
|
vlan = DynamicModelChoiceField(
|
||||||
@ -297,14 +300,6 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
# Initialize helper selectors
|
|
||||||
instance = kwargs.get('instance')
|
|
||||||
initial = kwargs.get('initial', {}).copy()
|
|
||||||
if instance and instance.vlan is not None:
|
|
||||||
initial['vlan_group'] = instance.vlan.group
|
|
||||||
kwargs['initial'] = initial
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['vrf'].empty_label = 'Global'
|
self.fields['vrf'].empty_label = 'Global'
|
||||||
@ -501,7 +496,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
|||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
display_field='display_name'
|
display_field='display_name',
|
||||||
|
initial_params={
|
||||||
|
'interfaces': '$interface'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
interface = DynamicModelChoiceField(
|
interface = DynamicModelChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
@ -512,7 +510,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
|||||||
)
|
)
|
||||||
virtual_machine = DynamicModelChoiceField(
|
virtual_machine = DynamicModelChoiceField(
|
||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
initial_params={
|
||||||
|
'interfaces': '$vminterface'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
vminterface = DynamicModelChoiceField(
|
vminterface = DynamicModelChoiceField(
|
||||||
queryset=VMInterface.objects.all(),
|
queryset=VMInterface.objects.all(),
|
||||||
@ -611,10 +612,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
|||||||
initial = kwargs.get('initial', {}).copy()
|
initial = kwargs.get('initial', {}).copy()
|
||||||
if instance:
|
if instance:
|
||||||
if type(instance.assigned_object) is Interface:
|
if type(instance.assigned_object) is Interface:
|
||||||
initial['device'] = instance.assigned_object.device
|
|
||||||
initial['interface'] = instance.assigned_object
|
initial['interface'] = instance.assigned_object
|
||||||
elif type(instance.assigned_object) is VMInterface:
|
elif type(instance.assigned_object) is VMInterface:
|
||||||
initial['virtual_machine'] = instance.assigned_object.virtual_machine
|
|
||||||
initial['vminterface'] = instance.assigned_object
|
initial['vminterface'] = instance.assigned_object
|
||||||
if instance.nat_inside:
|
if instance.nat_inside:
|
||||||
nat_inside_parent = instance.nat_inside.assigned_object
|
nat_inside_parent = instance.nat_inside.assigned_object
|
||||||
|
@ -119,7 +119,10 @@ class TenancyForm(forms.Form):
|
|||||||
tenant_group = DynamicModelChoiceField(
|
tenant_group = DynamicModelChoiceField(
|
||||||
queryset=TenantGroup.objects.all(),
|
queryset=TenantGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None'
|
null_option='None',
|
||||||
|
initial_params={
|
||||||
|
'tenants': '$tenant'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
tenant = DynamicModelChoiceField(
|
tenant = DynamicModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
@ -129,17 +132,6 @@ class TenancyForm(forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
# Initialize helper selector
|
|
||||||
instance = kwargs.get('instance')
|
|
||||||
if instance and instance.tenant is not None:
|
|
||||||
initial = kwargs.get('initial', {}).copy()
|
|
||||||
initial['tenant_group'] = instance.tenant.group
|
|
||||||
kwargs['initial'] = initial
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TenancyFilterForm(forms.Form):
|
class TenancyFilterForm(forms.Form):
|
||||||
tenant_group = DynamicModelMultipleChoiceField(
|
tenant_group = DynamicModelMultipleChoiceField(
|
||||||
|
@ -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 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 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 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
|
:param disabled_indicator: The name of the field which, if populated, will disable selection of the
|
||||||
choice (optional)
|
choice (optional)
|
||||||
@ -256,10 +257,11 @@ class DynamicModelChoiceMixin:
|
|||||||
filter = django_filters.ModelChoiceFilter
|
filter = django_filters.ModelChoiceFilter
|
||||||
widget = widgets.APISelect
|
widget = widgets.APISelect
|
||||||
|
|
||||||
def __init__(self, display_field='name', query_params=None, null_option=None, disabled_indicator=None,
|
def __init__(self, display_field='name', query_params=None, initial_params=None, null_option=None,
|
||||||
brief_mode=True, *args, **kwargs):
|
disabled_indicator=None, brief_mode=True, *args, **kwargs):
|
||||||
self.display_field = display_field
|
self.display_field = display_field
|
||||||
self.query_params = query_params or {}
|
self.query_params = query_params or {}
|
||||||
|
self.initial_params = initial_params or {}
|
||||||
self.null_option = null_option
|
self.null_option = null_option
|
||||||
self.disabled_indicator = disabled_indicator
|
self.disabled_indicator = disabled_indicator
|
||||||
self.brief_mode = brief_mode
|
self.brief_mode = brief_mode
|
||||||
@ -300,6 +302,16 @@ class DynamicModelChoiceMixin:
|
|||||||
def get_bound_field(self, form, field_name):
|
def get_bound_field(self, form, field_name):
|
||||||
bound_field = BoundField(form, self, 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
|
||||||
|
if filter_kwargs:
|
||||||
|
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
|
# 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.
|
# will be populated on-demand via the APISelect widget.
|
||||||
data = bound_field.value()
|
data = bound_field.value()
|
||||||
|
@ -266,7 +266,10 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
cluster_group = DynamicModelChoiceField(
|
cluster_group = DynamicModelChoiceField(
|
||||||
queryset=ClusterGroup.objects.all(),
|
queryset=ClusterGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None'
|
null_option='None',
|
||||||
|
initial_params={
|
||||||
|
'clusters': '$cluster'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
cluster = DynamicModelChoiceField(
|
cluster = DynamicModelChoiceField(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
@ -311,14 +314,6 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
# Initialize helper selector
|
|
||||||
instance = kwargs.get('instance')
|
|
||||||
if instance.pk and instance.cluster is not None:
|
|
||||||
initial = kwargs.get('initial', {}).copy()
|
|
||||||
initial['cluster_group'] = instance.cluster.group
|
|
||||||
kwargs['initial'] = initial
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
|
Loading…
Reference in New Issue
Block a user