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:
Jeremy Stretch 2020-11-04 13:20:30 -05:00 committed by GitHub
commit a69d61c124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 52 additions and 85 deletions

View File

@ -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(

View File

@ -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):

View File

@ -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

View File

@ -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(

View File

@ -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()

View File

@ -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: