From fa95191792e6d609a42e781e5c3856ecab04f564 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 29 Aug 2017 15:26:35 -0400 Subject: [PATCH] Allowed assigning an IP address to either a device or a VM --- netbox/dcim/models.py | 4 + netbox/ipam/forms.py | 89 ++++++------------- netbox/ipam/tables.py | 10 +-- netbox/ipam/views.py | 15 +++- netbox/templates/dcim/inc/interface.html | 2 +- netbox/templates/ipam/ipaddress.html | 4 +- netbox/templates/ipam/ipaddress_edit.html | 30 ++++--- .../virtualization/inc/interface.html | 7 +- .../virtualization/virtualmachine.html | 30 +++++++ 9 files changed, 106 insertions(+), 85 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index adf0d80a3..5e89ea6c2 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1241,6 +1241,10 @@ class Interface(models.Model): ) }) + @property + def parent(self): + return self.device or self.virtual_machine + @property def is_virtual(self): return self.form_factor in VIRTUAL_IFACE_TYPES diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index e19376e8e..8eabdd9a9 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -377,50 +377,9 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm): # class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm): - interface_site = forms.ModelChoiceField( - queryset=Site.objects.all(), - required=False, - label='Site', - widget=forms.Select( - attrs={'filter-for': 'interface_rack'} - ) - ) - interface_rack = ChainedModelChoiceField( - queryset=Rack.objects.all(), - chains=( - ('site', 'interface_site'), - ), - required=False, - label='Rack', - widget=APISelect( - api_url='/api/dcim/racks/?site_id={{interface_site}}', - display_field='display_name', - attrs={'filter-for': 'interface_device', 'nullable': 'true'} - ) - ) - interface_device = ChainedModelChoiceField( - queryset=Device.objects.all(), - chains=( - ('site', 'interface_site'), - ('rack', 'interface_rack'), - ), - 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'} - ) - ) - interface = ChainedModelChoiceField( + interface = forms.ModelChoiceField( queryset=Interface.objects.all(), - chains=( - ('device', 'interface_device'), - ), - required=False, - widget=APISelect( - api_url='/api/dcim/interfaces/?device_id={{interface_device}}' - ) + required=False ) nat_site = forms.ModelChoiceField( queryset=Site.objects.all(), @@ -479,13 +438,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) obj_label='address' ) ) - primary_for_device = forms.BooleanField(required=False, label='Make this the primary IP for the device') + primary_for_parent = forms.BooleanField(required=False, label='Make this the primary IP for the device/VM') class Meta: model = IPAddress fields = [ - 'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack', - 'nat_inside', 'tenant_group', 'tenant', + 'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site', + 'nat_rack', 'nat_inside', 'tenant_group', 'tenant', ] def __init__(self, *args, **kwargs): @@ -493,10 +452,6 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) # Initialize helper selectors instance = kwargs.get('instance') initial = kwargs.get('initial', {}).copy() - if instance and instance.interface is not None: - initial['interface_site'] = instance.interface.device.site - initial['interface_rack'] = instance.interface.device.rack - initial['interface_device'] = instance.interface.device if instance and instance.nat_inside and instance.nat_inside.device is not None: initial['nat_site'] = instance.nat_inside.device.site initial['nat_rack'] = instance.nat_inside.device.rack @@ -507,22 +462,30 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) self.fields['vrf'].empty_label = 'Global' - # Initialize primary_for_device if IP address is already assigned - if self.instance.interface is not None: - device = self.instance.interface.device + # Limit interface selections to those belonging to the parent device/VM + if self.instance and self.instance.interface: + self.fields['interface'].queryset = Interface.objects.filter( + device=self.instance.interface.device, virtual_machine=self.instance.interface.virtual_machine + ) + else: + self.fields['interface'].choices = [] + + # Initialize primary_for_parent if IP address is already assigned + if self.instance.pk and self.instance.interface is not None: + parent = self.instance.interface.parent if ( - self.instance.address.version == 4 and device.primary_ip4 == self.instance or - self.instance.address.version == 6 and device.primary_ip6 == self.instance + self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or + self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk ): - self.initial['primary_for_device'] = True + self.initial['primary_for_parent'] = True def clean(self): super(IPAddressForm, self).clean() # Primary IP assignment is only available if an interface has been assigned. - if self.cleaned_data.get('primary_for_device') and not self.cleaned_data.get('interface'): + if self.cleaned_data.get('primary_for_parent') and not self.cleaned_data.get('interface'): self.add_error( - 'primary_for_device', "Only IP addresses assigned to an interface can be designated as primary IPs." + 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs." ) def save(self, *args, **kwargs): @@ -530,13 +493,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) ipaddress = super(IPAddressForm, self).save(*args, **kwargs) # Assign this IPAddress as the primary for the associated Device. - if self.cleaned_data['primary_for_device']: - device = self.cleaned_data['interface'].device + if self.cleaned_data['primary_for_parent']: + parent = self.cleaned_data['interface'].parent if ipaddress.address.version == 4: - device.primary_ip4 = ipaddress + parent.primary_ip4 = ipaddress else: - device.primary_ip6 = ipaddress - device.save() + parent.primary_ip6 = ipaddress + parent.save() # Clear assignment as primary for device if set. else: diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 96127aec5..4482dca05 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -77,9 +77,9 @@ IPADDRESS_LINK = """ {% endif %} """ -IPADDRESS_DEVICE = """ +IPADDRESS_PARENT = """ {% if record.interface %} - {{ record.interface.device }} + {{ record.interface.parent }} {% else %} — {% endif %} @@ -265,12 +265,12 @@ class IPAddressTable(BaseTable): status = tables.TemplateColumn(STATUS_LABEL) vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF') tenant = tables.TemplateColumn(TENANT_LINK) - device = tables.TemplateColumn(IPADDRESS_DEVICE, orderable=False) + parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False) interface = tables.Column(orderable=False) class Meta(BaseTable.Meta): model = IPAddress - fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'device', 'interface', 'description') + fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description') row_attrs = { 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '', } @@ -283,7 +283,7 @@ class IPAddressDetailTable(IPAddressTable): class Meta(IPAddressTable.Meta): fields = ( - 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'device', 'interface', 'description', + 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'description', ) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 39834e306..d5cb06c0e 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.views.generic import View -from dcim.models import Device +from dcim.models import Device, Interface from utilities.paginator import EnhancedPaginator from utilities.views import ( BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, @@ -597,7 +597,7 @@ class IPAddressListView(ObjectListView): queryset = IPAddress.objects.select_related( 'vrf__tenant', 'tenant', 'nat_inside' ).prefetch_related( - 'interface__device' + 'interface__device', 'interface__virtual_machine' ) filter = filters.IPAddressFilter filter_form = forms.IPAddressFilterForm @@ -657,6 +657,17 @@ class IPAddressCreateView(PermissionRequiredMixin, ObjectEditView): template_name = 'ipam/ipaddress_edit.html' default_return_url = 'ipam:ipaddress_list' + def alter_obj(self, obj, request, url_args, url_kwargs): + + interface_id = request.GET.get('interface') + if interface_id: + try: + obj.interface = Interface.objects.get(pk=interface_id) + except (ValueError, Interface.DoesNotExist): + pass + + return obj + class IPAddressEditView(IPAddressCreateView): permission_required = 'ipam.change_ipaddress' diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 75d0f027d..e3febfefb 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -64,7 +64,7 @@ {% endif %} {% endif %} {% if perms.ipam.add_ipaddress %} - + {% endif %} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 789e89273..1d0786c6d 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -100,7 +100,7 @@ Assignment {% if ipaddress.interface %} - {{ ipaddress.interface.device }} ({{ ipaddress.interface }}) + {{ ipaddress.interface.parent }} ({{ ipaddress.interface }}) {% else %} None {% endif %} @@ -112,7 +112,7 @@ {% if ipaddress.nat_inside %} {{ ipaddress.nat_inside }} {% if ipaddress.nat_inside.interface %} - ({{ ipaddress.nat_inside.interface.device }}) + ({{ ipaddress.nat_inside.interface.parent }}) {% endif %} {% else %} None diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index 5a625e03c..d0dad69ee 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -1,6 +1,7 @@ {% extends 'utilities/obj_edit.html' %} {% load static from staticfiles %} {% load form_helpers %} +{% load helpers %} {% block tabs %} {% if not obj.pk %} @@ -26,18 +27,25 @@ {% render_field form.tenant %} -
-
- Interface Assignment + {% if obj.interface %} +
+
+ Interface Assignment +
+
+
+ + +
+ {% render_field form.interface %} + {% render_field form.primary_for_parent %} +
-
- {% render_field form.interface_site %} - {% render_field form.interface_rack %} - {% render_field form.interface_device %} - {% render_field form.interface %} - {% render_field form.primary_for_device %} -
-
+ {% endif %}
NAT IP (Inside)
diff --git a/netbox/templates/virtualization/inc/interface.html b/netbox/templates/virtualization/inc/interface.html index 5a08848da..01307c9ca 100644 --- a/netbox/templates/virtualization/inc/interface.html +++ b/netbox/templates/virtualization/inc/interface.html @@ -13,6 +13,11 @@ {{ iface.mtu|default:"" }} {{ iface.mac_address|default:"" }} + {% if perms.ipam.add_ipaddress %} + + + + {% endif %} {% if perms.dcim.change_interface %} @@ -30,7 +35,7 @@ {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} {% endif %} - + {{ ip }} {% if ip.description %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index cea8a0a83..5e0810fb4 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -79,6 +79,36 @@ {% endif %} + + Primary IPv4 + + {% if vm.primary_ip4 %} + {{ vm.primary_ip4.address.ip }} + {% if vm.primary_ip4.nat_inside %} + (NAT for {{ vm.primary_ip4.nat_inside.address.ip }}) + {% elif vm.primary_ip4.nat_outside %} + (NAT: {{ vm.primary_ip4.nat_outside.address.ip }}) + {% endif %} + {% else %} + N/A + {% endif %} + + + + Primary IPv6 + + {% if vm.primary_ip6 %} + {{ vm.primary_ip6.address.ip }} + {% if vm.primary_ip6.nat_inside %} + (NAT for {{ vm.primary_ip6.nat_inside.address.ip }}) + {% elif vm.primary_ip6.nat_outside %} + (NAT: {{ vm.primary_ip6.nat_outside.address.ip }}) + {% endif %} + {% else %} + N/A + {% endif %} + +