1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Fixes #1229: Fix validation error on forms where API search is used

This commit is contained in:
Jeremy Stretch
2017-05-25 14:33:50 -04:00
parent ebddc46bc0
commit 1dd5e2c926
12 changed files with 173 additions and 118 deletions

View File

@ -167,7 +167,9 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
required=False,
label='Rack',
widget=APISelect(
@ -177,7 +179,10 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
)
device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'site', 'rack': 'rack'},
chains=(
('site', 'site'),
('rack', 'rack'),
),
required=False,
label='Device',
widget=APISelect(
@ -186,20 +191,13 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
attrs={'filter-for': 'interface'}
)
)
livesearch = forms.CharField(
required=False,
label='Device',
widget=Livesearch(
query_key='q',
query_url='dcim-api:device-list',
field_to_update='device'
)
)
interface = ChainedModelChoiceField(
queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b'
),
chains={'device': 'device'},
chains=(
('device', 'device'),
),
required=False,
label='Interface',
widget=APISelect(
@ -210,8 +208,10 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
class Meta:
model = CircuitTermination
fields = ['term_side', 'site', 'rack', 'device', 'livesearch', 'interface', 'port_speed', 'upstream_speed',
'xconnect_id', 'pp_info']
fields = [
'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
'pp_info',
]
help_texts = {
'port_speed': "Physical circuit speed",
'xconnect_id': "ID of the local cross-connect",

View File

@ -190,7 +190,9 @@ class RackRoleForm(BootstrapMixin, forms.ModelForm):
class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm):
group = ChainedModelChoiceField(
queryset=RackGroup.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
required=False,
widget=APISelect(
api_url='/api/dcim/rack-groups/?site_id={{site}}',
@ -545,7 +547,9 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
required=False,
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
@ -570,7 +574,9 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
)
device_type = ChainedModelChoiceField(
queryset=DeviceType.objects.all(),
chains={'manufacturer': 'manufacturer'},
chains=(
('manufacturer', 'manufacturer'),
),
label='Device type',
widget=APISelect(
api_url='/api/dcim/device-types/?manufacturer_id={{manufacturer}}',
@ -957,20 +963,29 @@ class ConsoleConnectionImportForm(BootstrapMixin, BulkImportForm):
class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
widget=forms.HiddenInput(),
required=False,
widget=forms.Select(
attrs={'filter-for': 'rack'}
)
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
label='Rack',
required=False,
widget=forms.Select(
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'console_server', 'nullable': 'true'}
)
)
console_server = ChainedModelChoiceField(
queryset=Device.objects.filter(device_type__is_console_server=True),
chains={'site': 'site', 'rack': 'rack'},
chains=(
('site', 'site'),
('rack', 'rack'),
),
label='Console Server',
required=False,
widget=APISelect(
@ -990,7 +1005,9 @@ class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelF
)
cs_port = ChainedModelChoiceField(
queryset=ConsoleServerPort.objects.all(),
chains={'device': 'console_server'},
chains=(
('device', 'console_server'),
),
label='Port',
widget=APISelect(
api_url='/api/dcim/console-server-ports/?device_id={{console_server}}',
@ -1035,20 +1052,29 @@ class ConsoleServerPortCreateForm(DeviceComponentForm):
class ConsoleServerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
widget=forms.HiddenInput(),
required=False,
widget=forms.Select(
attrs={'filter-for': 'rack'}
)
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
label='Rack',
required=False,
widget=forms.Select(
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'device', 'nullable': 'true'}
)
)
device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'site', 'rack': 'rack'},
chains=(
('site', 'site'),
('rack', 'rack'),
),
label='Device',
required=False,
widget=APISelect(
@ -1068,7 +1094,9 @@ class ConsoleServerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.
)
port = ChainedModelChoiceField(
queryset=ConsolePort.objects.all(),
chains={'device': 'device'},
chains=(
('device', 'device'),
),
label='Port',
widget=APISelect(
api_url='/api/dcim/console-ports/?device_id={{device}}',
@ -1182,19 +1210,31 @@ class PowerConnectionImportForm(BootstrapMixin, BulkImportForm):
class PowerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.HiddenInput())
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site'},
label='Rack',
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False,
widget=forms.Select(
attrs={'filter-for': 'rack'}
)
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains=(
('site', 'site'),
),
label='Rack',
required=False,
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'pdu', 'nullable': 'true'}
)
)
pdu = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'site', 'rack': 'rack'},
chains=(
('site', 'site'),
('rack', 'rack'),
),
label='PDU',
required=False,
widget=APISelect(
@ -1214,7 +1254,9 @@ class PowerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
)
power_outlet = ChainedModelChoiceField(
queryset=PowerOutlet.objects.all(),
chains={'device': 'pdu'},
chains=(
('device', 'pdu'),
),
label='Outlet',
widget=APISelect(
api_url='/api/dcim/power-outlets/?device_id={{pdu}}',
@ -1259,20 +1301,29 @@ class PowerOutletCreateForm(DeviceComponentForm):
class PowerOutletConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
widget=forms.HiddenInput()
required=False,
widget=forms.Select(
attrs={'filter-for': 'rack'}
)
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
label='Rack',
required=False,
widget=forms.Select(
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'device', 'nullable': 'true'}
)
)
device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'site', 'rack': 'rack'},
chains=(
('site', 'site'),
('rack', 'rack'),
),
label='Device',
required=False,
widget=APISelect(
@ -1292,7 +1343,9 @@ class PowerOutletConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
)
port = ChainedModelChoiceField(
queryset=PowerPort.objects.all(),
chains={'device': 'device'},
chains=(
('device', 'device'),
),
label='Port',
widget=APISelect(
api_url='/api/dcim/power-ports/?device_id={{device}}',
@ -1412,7 +1465,9 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
)
rack_b = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'site_b'},
chains=(
('site', 'site_b'),
),
label='Rack',
required=False,
widget=APISelect(
@ -1422,7 +1477,10 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
)
device_b = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'site_b', 'rack': 'rack_b'},
chains=(
('site', 'site_b'),
('rack', 'rack_b'),
),
label='Device',
required=False,
widget=APISelect(
@ -1444,7 +1502,9 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b'
),
chains={'device': 'device_b'},
chains=(
('device', 'device_b'),
),
label='Interface',
widget=APISelect(
api_url='/api/dcim/interfaces/?device_id={{device_b}}&type=physical',

View File

@ -944,9 +944,9 @@ def consoleport_connect(request, pk):
else:
form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
'site': request.GET.get('site', consoleport.device.site),
'rack': request.GET.get('rack', None),
'console_server': request.GET.get('console_server', None),
'site': request.GET.get('site'),
'rack': request.GET.get('rack'),
'console_server': request.GET.get('console_server'),
'connection_status': CONNECTION_STATUS_CONNECTED,
})
@ -1061,9 +1061,9 @@ def consoleserverport_connect(request, pk):
else:
form = forms.ConsoleServerPortConnectionForm(initial={
'site': request.GET.get('site', consoleserverport.device.site),
'rack': request.GET.get('rack', None),
'device': request.GET.get('device', None),
'site': request.GET.get('site'),
'rack': request.GET.get('rack'),
'device': request.GET.get('device'),
'connection_status': CONNECTION_STATUS_CONNECTED,
})
@ -1167,9 +1167,9 @@ def powerport_connect(request, pk):
else:
form = forms.PowerPortConnectionForm(instance=powerport, initial={
'site': request.GET.get('site', powerport.device.site),
'rack': request.GET.get('rack', None),
'pdu': request.GET.get('pdu', None),
'site': request.GET.get('site'),
'rack': request.GET.get('rack'),
'pdu': request.GET.get('pdu'),
'connection_status': CONNECTION_STATUS_CONNECTED,
})
@ -1284,9 +1284,9 @@ def poweroutlet_connect(request, pk):
else:
form = forms.PowerOutletConnectionForm(initial={
'site': request.GET.get('site', poweroutlet.device.site),
'rack': request.GET.get('rack', None),
'device': request.GET.get('device', None),
'site': request.GET.get('site'),
'rack': request.GET.get('rack'),
'device': request.GET.get('device'),
'connection_status': CONNECTION_STATUS_CONNECTED,
})
@ -1616,11 +1616,11 @@ def interfaceconnection_add(request, pk):
else:
form = forms.InterfaceConnectionForm(device, initial={
'interface_a': request.GET.get('interface_a', None),
'site_b': request.GET.get('site_b', device.site),
'rack_b': request.GET.get('rack_b', None),
'device_b': request.GET.get('device_b', None),
'interface_b': request.GET.get('interface_b', None),
'interface_a': request.GET.get('interface_a'),
'site_b': request.GET.get('site_b'),
'rack_b': request.GET.get('rack_b'),
'device_b': request.GET.get('device_b'),
'interface_b': request.GET.get('interface_b'),
})
return render(request, 'dcim/interfaceconnection_edit.html', {

View File

@ -168,12 +168,21 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
site = forms.ModelChoiceField(
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
queryset=Site.objects.all(),
required=False,
label='Site',
widget=forms.Select(
attrs={'filter-for': 'vlan', 'nullable': 'true'}
)
)
vlan = ChainedModelChoiceField(
queryset=VLAN.objects.all(), chains={'site': 'site'}, required=False, label='VLAN', widget=APISelect(
queryset=VLAN.objects.all(),
chains=(
('site', 'site'),
),
required=False,
label='VLAN',
widget=APISelect(
api_url='/api/ipam/vlans/?site_id={{site}}', display_field='display_name'
)
)
@ -322,7 +331,9 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
)
interface_rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'interface_site'},
chains=(
('site', 'interface_site'),
),
required=False,
label='Rack',
widget=APISelect(
@ -333,7 +344,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
)
interface_device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'interface_site', 'rack': 'interface_rack'},
chains=(
('site', 'interface_site'),
('rack', 'interface_rack'),
),
required=False,
label='Device',
widget=APISelect(
@ -344,7 +358,9 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
)
interface = ChainedModelChoiceField(
queryset=Interface.objects.all(),
chains={'device': 'interface_device'},
chains=(
('device', 'interface_device'),
),
required=False,
widget=APISelect(
api_url='/api/dcim/interfaces/?device_id={{interface_device}}'
@ -355,34 +371,41 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
required=False,
label='Site',
widget=forms.Select(
attrs={'filter-for': 'nat_device'}
attrs={'filter-for': 'nat_rack'}
)
)
nat_rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains={'site': 'nat_site'},
chains=(
('site', 'nat_site'),
),
required=False,
label='Rack',
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{interface_site}}',
api_url='/api/dcim/racks/?site_id={{nat_site}}',
display_field='display_name',
attrs={'filter-for': 'nat_device', 'nullable': 'true'}
)
)
nat_device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains={'site': 'nat_site'},
chains=(
('site', 'nat_site'),
('rack', 'nat_rack'),
),
required=False,
label='Device',
widget=APISelect(
api_url='/api/dcim/devices/?site_id={{nat_site}}',
api_url='/api/dcim/devices/?site_id={{nat_site}}&rack_id={{nat_rack}}',
display_field='display_name',
attrs={'filter-for': 'nat_inside'}
)
)
nat_inside = ChainedModelChoiceField(
queryset=IPAddress.objects.all(),
chains={'interface__device': 'nat_device'},
chains=(
('interface__device', 'nat_device'),
),
required=False,
label='IP Address',
widget=APISelect(
@ -392,7 +415,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
)
livesearch = forms.CharField(
required=False,
label='IP Address',
label='Search',
widget=Livesearch(
query_key='q',
query_url='ipam-api:ipaddress-list',
@ -405,8 +428,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
class Meta:
model = IPAddress
fields = [
'address', 'vrf', 'status', 'description', 'interface', 'primary_for_device', 'nat_inside', 'tenant_group',
'tenant',
'address', 'vrf', 'status', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack',
'nat_inside', 'tenant_group', 'tenant',
]
def __init__(self, *args, **kwargs):
@ -627,7 +650,9 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
)
group = ChainedModelChoiceField(
queryset=VLANGroup.objects.all(),
chains={'site': 'site'},
chains=(
('site', 'site'),
),
required=False,
label='Group',
widget=APISelect(

View File

@ -45,23 +45,8 @@
</div>
</div>
{% render_field form.site %}
<div class="row">
<div class="col-md-9 col-md-offset-3">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
<li role="presentation"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
</ul>
</div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="select">
{% render_field form.rack %}
{% render_field form.device %}
</div>
<div class="tab-pane" id="search">
{% render_field form.livesearch %}
</div>
</div>
{% render_field form.interface %}
</div>
</div>

View File

@ -32,12 +32,7 @@
{% render_field form.livesearch %}
</div>
<div class="tab-pane" id="select">
<div class="form-group">
<label class="col-md-3 control-label">Site</label>
<div class="col-md-9">
<p class="form-control-static">{{ consoleport.device.site }}</p>
</div>
</div>
{% render_field form.site %}
{% render_field form.rack %}
{% render_field form.console_server %}
</div>

View File

@ -32,12 +32,7 @@
{% render_field form.livesearch %}
</div>
<div class="tab-pane" id="select">
<div class="form-group">
<label class="col-md-3 control-label">Site</label>
<div class="col-md-9">
<p class="form-control-static">{{ consoleserverport.device.site }}</p>
</div>
</div>
{% render_field form.site %}
{% render_field form.rack %}
{% render_field form.device %}
</div>

View File

@ -32,12 +32,7 @@
{% render_field form.livesearch %}
</div>
<div class="tab-pane" id="select">
<div class="form-group">
<label class="col-md-3 control-label">Site</label>
<div class="col-md-9">
<p class="form-control-static">{{ poweroutlet.device.site }}</p>
</div>
</div>
{% render_field form.site %}
{% render_field form.rack %}
{% render_field form.device %}
</div>

View File

@ -32,12 +32,7 @@
{% render_field form.livesearch %}
</div>
<div class="tab-pane" id="select">
<div class="form-group">
<label class="col-md-3 control-label">Site</label>
<div class="col-md-9">
<p class="form-control-static">{{ powerport.device.site }}</p>
</div>
</div>
{% render_field form.site %}
{% render_field form.rack %}
{% render_field form.pdu %}
</div>

View File

@ -47,6 +47,7 @@
<div class="tab-content">
<div class="tab-pane active" id="select">
{% render_field form.nat_site %}
{% render_field form.nat_rack %}
{% render_field form.nat_device %}
</div>
<div class="tab-pane" id="search">

View File

@ -81,7 +81,9 @@ class TenancyForm(ChainedFieldsMixin, forms.Form):
)
tenant = ChainedModelChoiceField(
queryset=Tenant.objects.all(),
chains={'group': 'tenant_group'},
chains=(
('group', 'tenant_group'),
),
required=False,
widget=APISelect(
api_url='/api/tenancy/tenants/?group_id={{tenant_group}}'

View File

@ -443,17 +443,19 @@ class ChainedFieldsMixin(forms.BaseForm):
if isinstance(field, ChainedModelChoiceField):
filters_dict = {}
for db_field, parent_field in field.chains.items():
for (db_field, parent_field) in field.chains:
if self.is_bound and self.data.get(parent_field):
filters_dict[db_field] = self.data[parent_field]
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
if filters_dict:
field.queryset = field.queryset.filter(**filters_dict)
else:
elif not self.is_bound:
field.queryset = field.queryset.none()