From 09000ad9b3a486d5ef0708f1160ed8dabb9a6295 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Apr 2017 14:54:17 -0400 Subject: [PATCH] Closes #1001: Merged IP interface assignment into ipam.IPAddressForm --- netbox/dcim/forms.py | 30 --------- netbox/dcim/urls.py | 1 - netbox/dcim/views.py | 41 ------------ netbox/ipam/forms.py | 81 ++++++++++++++++++----- netbox/ipam/urls.py | 2 - netbox/ipam/views.py | 69 ------------------- netbox/templates/dcim/inc/interface.html | 2 +- netbox/templates/ipam/ipaddress.html | 6 -- netbox/templates/ipam/ipaddress_edit.html | 41 +++--------- netbox/utilities/views.py | 4 +- 10 files changed, 81 insertions(+), 196 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 7a4d093c4..776976e62 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1691,36 +1691,6 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): device = forms.CharField(required=False, label='Device name') -# -# IP addresses -# - -class IPAddressForm(BootstrapMixin, CustomFieldForm): - set_as_primary = forms.BooleanField(label='Set as primary IP for device', required=False) - - class Meta: - model = IPAddress - fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'description'] - - def __init__(self, device, *args, **kwargs): - - super(IPAddressForm, self).__init__(*args, **kwargs) - - self.fields['vrf'].empty_label = 'Global' - - interfaces = device.interfaces.order_naturally(method=device.device_type.interface_ordering) - self.fields['interface'].queryset = interfaces - self.fields['interface'].required = True - - # If this device has only one interface, select it by default. - if 'interface' not in self.initial and len(interfaces) == 1: - self.fields['interface'].initial = interfaces[0] - - # If this device does not have any IP addresses assigned, default to setting the first IP as its primary. - if not IPAddress.objects.filter(interface__device=device).count(): - self.fields['set_as_primary'].initial = True - - # # Modules # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c52807aa3..e3a29147f 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -116,7 +116,6 @@ urlpatterns = [ url(r'^devices/(?P\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'), url(r'^devices/(?P\d+)/inventory/$', views.device_inventory, name='device_inventory'), url(r'^devices/(?P\d+)/lldp-neighbors/$', views.device_lldp_neighbors, name='device_lldp_neighbors'), - url(r'^devices/(?P\d+)/ip-addresses/assign/$', views.ipaddress_assign, name='ipaddress_assign'), url(r'^devices/(?P\d+)/add-secret/$', secret_add, name='device_addsecret'), url(r'^devices/(?P\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 6bf5ececd..066faf3d1 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1561,47 +1561,6 @@ class InterfaceConnectionsListView(ObjectListView): template_name = 'dcim/interface_connections_list.html' -# -# IP addresses -# - -@permission_required(['dcim.change_device', 'ipam.add_ipaddress']) -def ipaddress_assign(request, pk): - - device = get_object_or_404(Device, pk=pk) - - if request.method == 'POST': - form = forms.IPAddressForm(device, request.POST) - if form.is_valid(): - - ipaddress = form.save(commit=False) - ipaddress.interface = form.cleaned_data['interface'] - ipaddress.save() - form.save_custom_fields() - messages.success(request, u"Added new IP address {} to interface {}.".format(ipaddress, ipaddress.interface)) - - if form.cleaned_data['set_as_primary']: - if ipaddress.family == 4: - device.primary_ip4 = ipaddress - elif ipaddress.family == 6: - device.primary_ip6 = ipaddress - device.save() - - if '_addanother' in request.POST: - return redirect('dcim:ipaddress_assign', pk=device.pk) - else: - return redirect('dcim:device', pk=device.pk) - - else: - form = forms.IPAddressForm(device, initial=request.GET) - - return render(request, 'dcim/ipaddress_assign.html', { - 'device': device, - 'form': form, - 'return_url': reverse('dcim:device', kwargs={'pk': device.pk}), - }) - - # # Modules # diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index ec324a8b0..c9701f60b 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -6,7 +6,7 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi from tenancy.models import Tenant from utilities.forms import ( APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch, - SlugField, add_blank_choice, + ReturnURLForm, SlugField, add_blank_choice, ) from .models import ( @@ -307,21 +307,46 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm): # IP addresses # -class IPAddressForm(BootstrapMixin, CustomFieldForm): - nat_site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site', - widget=forms.Select(attrs={'filter-for': 'nat_device'})) - nat_device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device', - widget=APISelect(api_url='/api/dcim/devices/?site_id={{nat_site}}', - display_field='display_name', - attrs={'filter-for': 'nat_inside'})) - livesearch = forms.CharField(required=False, label='IP Address', widget=Livesearch( - query_key='q', query_url='ipam-api:ipaddress_list', field_to_update='nat_inside', obj_label='address') +class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm): + interface_site = forms.ModelChoiceField( + queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select( + attrs={'filter-for': 'interface_rack'} + ) + ) + interface_rack = forms.ModelChoiceField( + queryset=Rack.objects.all(), required=False, label='Rack', widget=APISelect( + api_url='/api/dcim/racks/?site_id={{interface_site}}', display_field='display_name', + attrs={'filter-for': 'interface_device'} + ) + ) + interface_device = forms.ModelChoiceField( + queryset=Device.objects.all(), required=False, label='Device', widget=APISelect( + api_url='/api/dcim/devices/?site_id={{interface_site}}&rack_id={{interface_rack}}', + display_field='display_name', attrs={'filter-for': 'interface'} + ) + ) + nat_site = forms.ModelChoiceField( + queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select( + attrs={'filter-for': 'nat_device'} + ) + ) + nat_device = forms.ModelChoiceField( + queryset=Device.objects.all(), required=False, label='Device', widget=APISelect( + api_url='/api/dcim/devices/?site_id={{nat_site}}', display_field='display_name', + attrs={'filter-for': 'nat_inside'} + ) + ) + livesearch = forms.CharField( + required=False, label='IP Address', widget=Livesearch( + query_key='q', query_url='ipam-api:ipaddress_list', field_to_update='nat_inside', obj_label='address' + ) ) class Meta: model = IPAddress - fields = ['address', 'vrf', 'tenant', 'status', 'nat_inside', 'description'] + fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'nat_inside', 'description'] widgets = { + 'interface': APISelect(api_url='/api/dcim/devices/{{interface_device}}/interfaces/'), 'nat_inside': APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', display_field='address') } @@ -330,8 +355,37 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm): self.fields['vrf'].empty_label = 'Global' - if self.instance.nat_inside: + # If an interface has been assigned, initialize site, rack, and device + if self.instance.interface: + self.initial['interface_site'] = self.instance.interface.device.site + self.initial['interface_rack'] = self.instance.interface.device.rack + self.initial['interface_device'] = self.instance.interface.device + # Limit rack choices + if self.is_bound and self.data.get('interface_site'): + self.fields['interface_rack'].queryset = Rack.objects.filter(site__pk=self.data['interface_site']) + elif self.initial.get('interface_site'): + self.fields['interface_rack'].queryset = Rack.objects.filter(site=self.initial['interface_site']) + else: + self.fields['interface_rack'].choices = [] + + # Limit device choices + if self.is_bound and self.data.get('interface_rack'): + self.fields['interface_device'].queryset = Device.objects.filter(rack=self.data['interface_rack']) + elif self.initial.get('interface_rack'): + self.fields['interface_device'].queryset = Device.objects.filter(rack=self.initial['interface_rack']) + else: + self.fields['interface_device'].choices = [] + + # Limit interface choices + if self.is_bound and self.data.get('interface_device'): + self.fields['interface'].queryset = Interface.objects.filter(device=self.data['interface_device']) + elif self.initial.get('interface_device'): + self.fields['interface'].queryset = Interface.objects.filter(device=self.initial['interface_device']) + else: + self.fields['interface'].choices = [] + + if self.instance.nat_inside: nat_inside = self.instance.nat_inside # If the IP is assigned to an interface, populate site/device fields accordingly if self.instance.nat_inside.interface: @@ -345,9 +399,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm): ) else: self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk) - else: - # Initialize nat_device choices if nat_site is set if self.is_bound and self.data.get('nat_site'): self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site']) @@ -355,7 +407,6 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm): self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site']) else: self.fields['nat_device'].choices = [] - # Initialize nat_inside choices if nat_device is set if self.is_bound and self.data.get('nat_device'): self.fields['nat_inside'].queryset = IPAddress.objects.filter( diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 5ef052b37..41d684360 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -57,8 +57,6 @@ urlpatterns = [ url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'), url(r'^ip-addresses/(?P\d+)/$', views.ipaddress, name='ipaddress'), url(r'^ip-addresses/(?P\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'), - url(r'^ip-addresses/(?P\d+)/assign/$', views.ipaddress_assign, name='ipaddress_assign'), - url(r'^ip-addresses/(?P\d+)/remove/$', views.ipaddress_remove, name='ipaddress_remove'), url(r'^ip-addresses/(?P\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'), # VLAN groups diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 2d2e1f044..0c53b8bc9 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -571,75 +571,6 @@ def ipaddress(request, pk): }) -@permission_required(['dcim.change_device', 'ipam.change_ipaddress']) -def ipaddress_assign(request, pk): - - ipaddress = get_object_or_404(IPAddress, pk=pk) - - if request.method == 'POST': - form = forms.IPAddressAssignForm(request.POST) - if form.is_valid(): - - interface = form.cleaned_data['interface'] - ipaddress.interface = interface - ipaddress.save() - messages.success(request, u"Assigned IP address {} to interface {}.".format(ipaddress, ipaddress.interface)) - - if form.cleaned_data['set_as_primary']: - device = interface.device - if ipaddress.family == 4: - device.primary_ip4 = ipaddress - elif ipaddress.family == 6: - device.primary_ip6 = ipaddress - device.save() - - return redirect('ipam:ipaddress', pk=ipaddress.pk) - else: - assert False, form.errors - - else: - form = forms.IPAddressAssignForm() - - return render(request, 'ipam/ipaddress_assign.html', { - 'ipaddress': ipaddress, - 'form': form, - 'return_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}), - }) - - -@permission_required(['dcim.change_device', 'ipam.change_ipaddress']) -def ipaddress_remove(request, pk): - - ipaddress = get_object_or_404(IPAddress, pk=pk) - - if request.method == 'POST': - form = ConfirmationForm(request.POST) - if form.is_valid(): - - device = ipaddress.interface.device - ipaddress.interface = None - ipaddress.save() - messages.success(request, u"Removed IP address {} from {}.".format(ipaddress, device)) - - if device.primary_ip4 == ipaddress.pk: - device.primary_ip4 = None - device.save() - elif device.primary_ip6 == ipaddress.pk: - device.primary_ip6 = None - device.save() - - return redirect('ipam:ipaddress', pk=ipaddress.pk) - - else: - form = ConfirmationForm() - - return render(request, 'ipam/ipaddress_unassign.html', { - 'ipaddress': ipaddress, - 'form': form, - 'return_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}), - }) - - class IPAddressEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'ipam.change_ipaddress' model = IPAddress diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 7fe068304..39f2947c5 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -59,7 +59,7 @@ {% endif %} {% endif %} {% if perms.ipam.add_ipaddress %} - + {% endif %} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index c4ed601c6..357ea5dac 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -98,14 +98,8 @@ {% if ipaddress.interface %} {{ ipaddress.interface.device }} ({{ ipaddress.interface }}) - {% if perms.dcim.change_device and perms.ipam.change_ipaddress %} - Remove - {% endif %} {% else %} None - {% if perms.dcim.change_device and perms.ipam.change_ipaddress %} - Assign - {% endif %} {% endif %} diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index 7226a75fc..82523eba0 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -16,39 +16,20 @@ {% render_field form.vrf %} {% render_field form.tenant %} {% render_field form.status %} - {% if obj.pk %} -
- -
-

- {% if obj.interface %} - {{ obj.interface.device }} - Remove - {% else %} - None - {% if obj.pk %} - Assign - {% endif %} - {% endif %} -

-
-
-
- -
-

- {% if obj.interface %} - {{ obj.interface }} - {% else %} - None - {% endif %} -

-
-
- {% endif %} {% render_field form.description %} +
+
+ Interface Assignment +
+
+ {% render_field form.interface_site %} + {% render_field form.interface_rack %} + {% render_field form.interface_device %} + {% render_field form.interface %} +
+
NAT IP (Inside)
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index c80bd4aae..0f867e16b 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -177,7 +177,9 @@ class ObjectEditView(GetReturnURLMixin, View): obj = self.get_object(kwargs) obj = self.alter_obj(obj, request, args, kwargs) - form = self.form_class(instance=obj, initial=request.GET) + # Parse initial data manually to avoid setting field values as lists + initial_data = {k: request.GET[k] for k in request.GET} + form = self.form_class(instance=obj, initial=initial_data) return render(request, self.template_name, { 'obj': obj,