mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Replace ChainedModelChoiceField with DynamicModelChoiceField
This commit is contained in:
@ -22,9 +22,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
||||||
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField, ConfirmationForm,
|
BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, DynamicModelChoiceField,
|
||||||
CSVChoiceField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, JSONField, SelectWithPK,
|
ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, JSONField, SelectWithPK, SmallTextarea, SlugField,
|
||||||
SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -472,11 +472,8 @@ class RackRoleCSVForm(forms.ModelForm):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||||
group = ChainedModelChoiceField(
|
group = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/rack-groups/',
|
api_url='/api/dcim/rack-groups/',
|
||||||
@ -761,13 +758,9 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
|
|
||||||
class RackElevationFilterForm(RackFilterForm):
|
class RackElevationFilterForm(RackFilterForm):
|
||||||
field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant']
|
field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant']
|
||||||
id = ChainedModelChoiceField(
|
id = FilterChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
label='Rack',
|
label='Rack',
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
('group_id', 'group_id'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url='/api/dcim/racks/',
|
api_url='/api/dcim/racks/',
|
||||||
@ -1706,11 +1699,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rack = ChainedModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/',
|
api_url='/api/dcim/racks/',
|
||||||
@ -1737,11 +1727,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
device_type = ChainedModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
chains=(
|
|
||||||
('manufacturer', 'manufacturer'),
|
|
||||||
),
|
|
||||||
label='Device type',
|
label='Device type',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/device-types/',
|
api_url='/api/dcim/device-types/',
|
||||||
@ -1761,11 +1748,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cluster = ChainedModelChoiceField(
|
cluster = DynamicModelChoiceField(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
chains=(
|
|
||||||
('group', 'cluster_group'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/virtualization/clusters/',
|
api_url='/api/virtualization/clusters/',
|
||||||
@ -3433,7 +3417,7 @@ class RearPortBulkDisconnectForm(ConfirmationForm):
|
|||||||
# Cables
|
# Cables
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Base form for connecting a Cable to a Device component
|
Base form for connecting a Cable to a Device component
|
||||||
"""
|
"""
|
||||||
@ -3449,11 +3433,8 @@ class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFo
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
termination_b_rack = ChainedModelChoiceField(
|
termination_b_rack = DynamicModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'termination_b_site'),
|
|
||||||
),
|
|
||||||
label='Rack',
|
label='Rack',
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -3466,12 +3447,8 @@ class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFo
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
termination_b_device = ChainedModelChoiceField(
|
termination_b_device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'termination_b_site'),
|
|
||||||
('rack', 'termination_b_rack'),
|
|
||||||
),
|
|
||||||
label='Device',
|
label='Device',
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -3569,7 +3546,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||||
termination_b_provider = forms.ModelChoiceField(
|
termination_b_provider = forms.ModelChoiceField(
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
label='Provider',
|
label='Provider',
|
||||||
@ -3581,7 +3558,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
termination_b_site = forms.ModelChoiceField(
|
termination_b_site = DynamicModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site',
|
label='Site',
|
||||||
required=False,
|
required=False,
|
||||||
@ -3592,11 +3569,8 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
termination_b_circuit = ChainedModelChoiceField(
|
termination_b_circuit = DynamicModelChoiceField(
|
||||||
queryset=Circuit.objects.all(),
|
queryset=Circuit.objects.all(),
|
||||||
chains=(
|
|
||||||
('provider', 'termination_b_provider'),
|
|
||||||
),
|
|
||||||
label='Circuit',
|
label='Circuit',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/circuits/circuits/',
|
api_url='/api/circuits/circuits/',
|
||||||
@ -3623,7 +3597,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
|
||||||
termination_b_site = forms.ModelChoiceField(
|
termination_b_site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site',
|
label='Site',
|
||||||
@ -3637,12 +3611,9 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
termination_b_rackgroup = ChainedModelChoiceField(
|
termination_b_rackgroup = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
label='Rack Group',
|
label='Rack Group',
|
||||||
chains=(
|
|
||||||
('site', 'termination_b_site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/rack-groups/',
|
api_url='/api/dcim/rack-groups/',
|
||||||
@ -3652,12 +3623,8 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
termination_b_powerpanel = ChainedModelChoiceField(
|
termination_b_powerpanel = DynamicModelChoiceField(
|
||||||
queryset=PowerPanel.objects.all(),
|
queryset=PowerPanel.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'termination_b_site'),
|
|
||||||
('rack_group', 'termination_b_rackgroup'),
|
|
||||||
),
|
|
||||||
label='Power Panel',
|
label='Power Panel',
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -4380,10 +4347,9 @@ class DeviceVCMembershipForm(forms.ModelForm):
|
|||||||
return vc_position
|
return vc_position
|
||||||
|
|
||||||
|
|
||||||
class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site',
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/dcim/sites/",
|
api_url="/api/dcim/sites/",
|
||||||
@ -4393,12 +4359,8 @@ class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rack = ChainedModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
label='Rack',
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/',
|
api_url='/api/dcim/racks/',
|
||||||
@ -4410,15 +4372,10 @@ class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
device = ChainedModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.filter(
|
queryset=Device.objects.filter(
|
||||||
virtual_chassis__isnull=True
|
virtual_chassis__isnull=True
|
||||||
),
|
),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
('rack', 'rack'),
|
|
||||||
),
|
|
||||||
label='Device',
|
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/devices/',
|
api_url='/api/dcim/devices/',
|
||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
@ -4490,11 +4447,8 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PowerPanelForm(BootstrapMixin, forms.ModelForm):
|
class PowerPanelForm(BootstrapMixin, forms.ModelForm):
|
||||||
rack_group = ChainedModelChoiceField(
|
rack_group = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/rack-groups/',
|
api_url='/api/dcim/rack-groups/',
|
||||||
@ -4595,7 +4549,7 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
site = ChainedModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
|
@ -10,9 +10,9 @@ from extras.forms import (
|
|||||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
|
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField,
|
||||||
CSVChoiceField, DatePicker, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm,
|
DatePicker, DynamicModelChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField,
|
||||||
SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES
|
ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import *
|
from .constants import *
|
||||||
@ -271,7 +271,6 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Site',
|
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/dcim/sites/",
|
api_url="/api/dcim/sites/",
|
||||||
filter_for={
|
filter_for={
|
||||||
@ -283,11 +282,8 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
vlan_group = ChainedModelChoiceField(
|
vlan_group = DynamicModelChoiceField(
|
||||||
queryset=VLANGroup.objects.all(),
|
queryset=VLANGroup.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
label='VLAN group',
|
label='VLAN group',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -300,12 +296,8 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
vlan = ChainedModelChoiceField(
|
vlan = DynamicModelChoiceField(
|
||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
('group', 'vlan_group'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
label='VLAN',
|
label='VLAN',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -603,11 +595,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
nat_rack = ChainedModelChoiceField(
|
nat_rack = DynamicModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'nat_site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
label='Rack',
|
label='Rack',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -621,12 +610,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
nat_device = ChainedModelChoiceField(
|
nat_device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'nat_site'),
|
|
||||||
('rack', 'nat_rack'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
label='Device',
|
label='Device',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -648,11 +633,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
nat_inside = ChainedModelChoiceField(
|
nat_inside = DynamicModelChoiceField(
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
chains=(
|
|
||||||
('interface__device', 'nat_device'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
label='IP Address',
|
label='IP Address',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -1102,13 +1084,9 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
group = ChainedModelChoiceField(
|
group = DynamicModelChoiceField(
|
||||||
queryset=VLANGroup.objects.all(),
|
queryset=VLANGroup.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
label='Group',
|
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/ipam/vlan-groups/',
|
api_url='/api/ipam/vlan-groups/',
|
||||||
)
|
)
|
||||||
|
@ -2,11 +2,11 @@ from django import forms
|
|||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
|
|
||||||
from extras.forms import (
|
from extras.forms import (
|
||||||
AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldFilterForm,
|
AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm,
|
||||||
)
|
)
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, APISelectMultiple, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField,
|
APISelect, APISelectMultiple, BootstrapMixin, CommentField, DynamicModelChoiceField, FilterChoiceField, SlugField,
|
||||||
FilterChoiceField, SlugField, TagFilterField
|
TagFilterField,
|
||||||
)
|
)
|
||||||
from .models import Tenant, TenantGroup
|
from .models import Tenant, TenantGroup
|
||||||
|
|
||||||
@ -121,8 +121,8 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
# Form extensions
|
# Form extensions
|
||||||
#
|
#
|
||||||
|
|
||||||
class TenancyForm(ChainedFieldsMixin, forms.Form):
|
class TenancyForm(forms.Form):
|
||||||
tenant_group = forms.ModelChoiceField(
|
tenant_group = DynamicModelChoiceField(
|
||||||
queryset=TenantGroup.objects.all(),
|
queryset=TenantGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@ -135,11 +135,8 @@ class TenancyForm(ChainedFieldsMixin, forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tenant = ChainedModelChoiceField(
|
tenant = DynamicModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
chains=(
|
|
||||||
('group', 'tenant_group'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/tenancy/tenants/'
|
api_url='/api/tenancy/tenants/'
|
||||||
|
@ -522,34 +522,6 @@ class FlexibleModelChoiceField(forms.ModelChoiceField):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ChainedModelChoiceField(forms.ModelChoiceField):
|
|
||||||
"""
|
|
||||||
A ModelChoiceField which is initialized based on the values of other fields within a form. `chains` is a dictionary
|
|
||||||
mapping of model fields to peer fields within the form. For example:
|
|
||||||
|
|
||||||
country1 = forms.ModelChoiceField(queryset=Country.objects.all())
|
|
||||||
city1 = ChainedModelChoiceField(queryset=City.objects.all(), chains={'country': 'country1'}
|
|
||||||
|
|
||||||
The queryset of the `city1` field will be modified as
|
|
||||||
|
|
||||||
.filter(country=<value>)
|
|
||||||
|
|
||||||
where <value> is the value of the `country1` field. (Note: The form must inherit from ChainedFieldsMixin.)
|
|
||||||
"""
|
|
||||||
def __init__(self, chains=None, *args, **kwargs):
|
|
||||||
self.chains = chains
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ChainedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
||||||
"""
|
|
||||||
See ChainedModelChoiceField
|
|
||||||
"""
|
|
||||||
def __init__(self, chains=None, *args, **kwargs):
|
|
||||||
self.chains = chains
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class SlugField(forms.SlugField):
|
class SlugField(forms.SlugField):
|
||||||
"""
|
"""
|
||||||
Extend the built-in SlugField to automatically populate from a field called `name` unless otherwise specified.
|
Extend the built-in SlugField to automatically populate from a field called `name` unless otherwise specified.
|
||||||
@ -578,16 +550,12 @@ class TagFilterField(forms.MultipleChoiceField):
|
|||||||
super().__init__(label='Tags', choices=get_choices, required=False, *args, **kwargs)
|
super().__init__(label='Tags', choices=get_choices, required=False, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FilterChoiceField(forms.ModelMultipleChoiceField):
|
class DynamicModelChoiceField(forms.ModelChoiceField):
|
||||||
"""
|
"""
|
||||||
Override get_bound_field() to avoid pre-populating field choices with a SQL query. The field will be
|
Override get_bound_field() to avoid pre-populating field choices with a SQL query. The field will be
|
||||||
rendered only with choices set via bound data. Choices are populated on-demand via the APISelect widget.
|
rendered only with choices set via bound data. Choices are populated on-demand via the APISelect widget.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
field_modifier = ''
|
||||||
# Filter fields are not required by default
|
|
||||||
if 'required' not in kwargs:
|
|
||||||
kwargs['required'] = False
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
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)
|
||||||
@ -595,7 +563,8 @@ class FilterChoiceField(forms.ModelMultipleChoiceField):
|
|||||||
# 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.
|
||||||
if bound_field.data:
|
if bound_field.data:
|
||||||
kwargs = {'{}__in'.format(self.to_field_name or 'pk'): bound_field.data}
|
field_name = '{}{}'.format(self.to_field_name or 'pk', self.field_modifier)
|
||||||
|
kwargs = {field_name: bound_field.data}
|
||||||
self.queryset = self.queryset.filter(**kwargs)
|
self.queryset = self.queryset.filter(**kwargs)
|
||||||
else:
|
else:
|
||||||
self.queryset = self.queryset.none()
|
self.queryset = self.queryset.none()
|
||||||
@ -603,6 +572,24 @@ class FilterChoiceField(forms.ModelMultipleChoiceField):
|
|||||||
return bound_field
|
return bound_field
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicModelMultipleChoiceField(DynamicModelChoiceField):
|
||||||
|
"""
|
||||||
|
A multiple-choice version of DynamicModelChoiceField.
|
||||||
|
"""
|
||||||
|
field_modifier = '__in'
|
||||||
|
|
||||||
|
|
||||||
|
class FilterChoiceField(DynamicModelMultipleChoiceField):
|
||||||
|
"""
|
||||||
|
A version of DynamicModelMultipleChoiceField which defaults to required=False.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# Filter fields are not required by default
|
||||||
|
if 'required' not in kwargs:
|
||||||
|
kwargs['required'] = False
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class LaxURLField(forms.URLField):
|
class LaxURLField(forms.URLField):
|
||||||
"""
|
"""
|
||||||
Modifies Django's built-in URLField in two ways:
|
Modifies Django's built-in URLField in two ways:
|
||||||
@ -655,46 +642,6 @@ class BootstrapMixin(forms.BaseForm):
|
|||||||
field.widget.attrs['placeholder'] = field.label
|
field.widget.attrs['placeholder'] = field.label
|
||||||
|
|
||||||
|
|
||||||
class ChainedFieldsMixin(forms.BaseForm):
|
|
||||||
"""
|
|
||||||
Iterate through all ChainedModelChoiceFields in the form and modify their querysets based on chained fields.
|
|
||||||
"""
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
for field_name, field in self.fields.items():
|
|
||||||
|
|
||||||
if isinstance(field, ChainedModelChoiceField):
|
|
||||||
|
|
||||||
filters_dict = {}
|
|
||||||
for (db_field, parent_field) in field.chains:
|
|
||||||
if self.is_bound and parent_field in self.data and self.data[parent_field]:
|
|
||||||
filters_dict[db_field] = self.data[parent_field] or None
|
|
||||||
elif self.initial.get(parent_field):
|
|
||||||
filters_dict[db_field] = self.initial[parent_field]
|
|
||||||
elif self.fields[parent_field].widget.attrs.get('nullable'):
|
|
||||||
filters_dict[db_field] = None
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Limit field queryset by chained field values
|
|
||||||
if filters_dict:
|
|
||||||
field.queryset = field.queryset.filter(**filters_dict)
|
|
||||||
# Editing an existing instance; limit field to its current value
|
|
||||||
elif not self.is_bound and getattr(self, 'instance', None) and hasattr(self.instance, field_name):
|
|
||||||
obj = getattr(self.instance, field_name)
|
|
||||||
if obj is not None:
|
|
||||||
field.queryset = field.queryset.filter(pk=obj.pk)
|
|
||||||
else:
|
|
||||||
field.queryset = field.queryset.none()
|
|
||||||
# Creating a new instance with no bound data; nullify queryset
|
|
||||||
elif not self.data.get(field_name):
|
|
||||||
field.queryset = field.queryset.none()
|
|
||||||
# Creating a new instance with bound data; limit queryset to the specified value
|
|
||||||
else:
|
|
||||||
field.queryset = field.queryset.filter(pk=self.data.get(field_name))
|
|
||||||
|
|
||||||
|
|
||||||
class ReturnURLForm(forms.Form):
|
class ReturnURLForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Provides a hidden return URL field to control where the user is directed after the form is submitted.
|
Provides a hidden return URL field to control where the user is directed after the form is submitted.
|
||||||
|
@ -14,9 +14,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||||
ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ConfirmationForm,
|
CommentField, ConfirmationForm, CSVChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||||
CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, SmallTextarea, StaticSelect2,
|
ExpandableNameField, FilterChoiceField, JSONField, SlugField, SmallTextarea, StaticSelect2, StaticSelect2Multiple,
|
||||||
StaticSelect2Multiple, TagFilterField,
|
TagFilterField,
|
||||||
)
|
)
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
@ -233,7 +233,7 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
|
||||||
region = forms.ModelChoiceField(
|
region = forms.ModelChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -247,11 +247,8 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
site = ChainedModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
chains=(
|
|
||||||
('region', 'region'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/sites/',
|
api_url='/api/dcim/sites/',
|
||||||
@ -261,11 +258,8 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rack = ChainedModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/',
|
api_url='/api/dcim/racks/',
|
||||||
@ -277,12 +271,8 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
devices = ChainedModelMultipleChoiceField(
|
devices = DynamicModelMultipleChoiceField(
|
||||||
queryset=Device.objects.filter(cluster__isnull=True),
|
queryset=Device.objects.filter(cluster__isnull=True),
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
('rack', 'rack'),
|
|
||||||
),
|
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url='/api/dcim/devices/',
|
api_url='/api/dcim/devices/',
|
||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
@ -342,11 +332,8 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cluster = ChainedModelChoiceField(
|
cluster = DynamicModelChoiceField(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
chains=(
|
|
||||||
('group', 'cluster_group'),
|
|
||||||
),
|
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/virtualization/clusters/'
|
api_url='/api/virtualization/clusters/'
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user